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 ## 🔧 Supabase OAuth Configuration
### 1. Access Your Supabase Dashboard ### 1. Access Your Supabase Dashboard
1. Go to [app.supabase.com](https://app.supabase.com) 1. Go to [app.supabase.com](https://app.supabase.com)
2. Select your project: `kmdeisowhtsalsekkzqd` 2. Select your project: `kmdeisowhtsalsekkzqd`
3. Navigate to **Authentication** > **Providers** 3. Navigate to **Authentication** > **Providers**
### 2. Configure Site URL ### 2. Configure Site URL
1. Go to **Authentication** > **Settings** 1. Go to **Authentication** > **Settings**
2. Set your Site URL to: `https://e7c3806a9bfe4bdf9bb8a72a7f0d31cd-324f24a826ec4eb198c1a0eef.fly.dev` 2. Set your Site URL to: `https://e7c3806a9bfe4bdf9bb8a72a7f0d31cd-324f24a826ec4eb198c1a0eef.fly.dev`
3. Add Redirect URLs: 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 ## 🐙 GitHub OAuth Setup
### 1. Create GitHub OAuth App ### 1. Create GitHub OAuth App
1. Go to [GitHub Developer Settings](https://github.com/settings/developers) 1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
2. Click **New OAuth App** 2. Click **New OAuth App**
3. Fill in the details: 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** 5. Copy the **Client ID** and **Client Secret**
### 2. Configure in Supabase ### 2. Configure in Supabase
1. In Supabase dashboard, go to **Authentication** > **Providers** 1. In Supabase dashboard, go to **Authentication** > **Providers**
2. Find **GitHub** and click to configure 2. Find **GitHub** and click to configure
3. Enable GitHub provider 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 ## 🌐 Google OAuth Setup
### 1. Create Google OAuth Credentials ### 1. Create Google OAuth Credentials
1. Go to [Google Cloud Console](https://console.cloud.google.com/) 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select existing one 2. Create a new project or select existing one
3. Enable **Google+ API** and **Google Identity API** 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** 8. Copy the **Client ID** and **Client Secret**
### 2. Configure in Supabase ### 2. Configure in Supabase
1. In Supabase dashboard, go to **Authentication** > **Providers** 1. In Supabase dashboard, go to **Authentication** > **Providers**
2. Find **Google** and click to configure 2. Find **Google** and click to configure
3. Enable Google provider 3. Enable Google provider
@ -74,6 +80,7 @@ Once configured:
## 🚀 Features Enabled ## 🚀 Features Enabled
With OAuth configured, users can: With OAuth configured, users can:
- **One-click login** with GitHub or Google - **One-click login** with GitHub or Google
- **Automatic profile setup** with avatar and name from OAuth provider - **Automatic profile setup** with avatar and name from OAuth provider
- **Seamless integration** with existing AeThex community platform - **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: Once set up, your AeThex app will have:
### 🔐 Authentication ### 🔐 Authentication
- Email/password sign up and sign in - Email/password sign up and sign in
- User session management - User session management
- Profile creation and updates - Profile creation and updates
### 🗄️ Database Features ### 🗄️ Database Features
- User profiles with extended information - User profiles with extended information
- Project portfolio management - Project portfolio management
- Achievement system with XP and levels - Achievement system with XP and levels
@ -230,16 +232,19 @@ Once set up, your AeThex app will have:
- Real-time notifications - Real-time notifications
### 🎮 Gamification ### 🎮 Gamification
- User levels and XP tracking - User levels and XP tracking
- Achievement system - Achievement system
- Progress tracking - Progress tracking
### 💬 Community ### 💬 Community
- User-generated content - User-generated content
- Real-time updates - Real-time updates
- Comment system - Comment system
### 📊 Dashboard ### 📊 Dashboard
- Personalized user dashboard - Personalized user dashboard
- Project tracking - Project tracking
- Achievement display - Achievement display
@ -255,6 +260,7 @@ Once set up, your AeThex app will have:
## 7. Production Deployment ## 7. Production Deployment
For production: For production:
1. Update your site URL in Supabase Authentication settings 1. Update your site URL in Supabase Authentication settings
2. Add your production domain to redirect URLs 2. Add your production domain to redirect URLs
3. Update your environment variables in your hosting platform 3. Update your environment variables in your hosting platform

View file

@ -1,8 +1,8 @@
import React from 'react'; import React from "react";
import { isSupabaseConfigured } from '@/lib/supabase'; import { isSupabaseConfigured } from "@/lib/supabase";
import { Alert, AlertDescription } from '@/components/ui/alert'; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from '@/components/ui/button'; import { Button } from "@/components/ui/button";
import { Database, ExternalLink, Info } from 'lucide-react'; import { Database, ExternalLink, Info } from "lucide-react";
export default function SupabaseStatus() { export default function SupabaseStatus() {
// Only show if Supabase is not configured (demo mode) // 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" className="border-blue-500/50 text-blue-300 hover:bg-blue-500/20"
onClick={() => { onClick={() => {
// Hide the status notification // Hide the status notification
const notification = document.querySelector('[data-supabase-status]'); const notification = document.querySelector(
"[data-supabase-status]",
);
if (notification) { if (notification) {
notification.style.display = 'none'; notification.style.display = "none";
} }
}} }}
> >
@ -39,7 +41,7 @@ export default function SupabaseStatus() {
size="sm" size="sm"
variant="outline" variant="outline"
className="border-blue-500/50 text-blue-300 hover:bg-blue-500/20" 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" /> <ExternalLink className="h-3 w-3 mr-1" />
Try Login Try Login

View file

@ -1,10 +1,14 @@
import React, { createContext, useContext, useEffect, useState } from 'react'; import React, { createContext, useContext, useEffect, useState } from "react";
import { User, Session } from '@supabase/supabase-js'; import { User, Session } from "@supabase/supabase-js";
import { supabase, isSupabaseConfigured } from '@/lib/supabase'; import { supabase, isSupabaseConfigured } from "@/lib/supabase";
import { UserProfile } from '@/lib/database.types'; import { UserProfile } from "@/lib/database.types";
import { aethexToast } from '@/lib/aethex-toast'; import { aethexToast } from "@/lib/aethex-toast";
import { DemoStorageService } from '@/lib/demo-storage'; import { DemoStorageService } from "@/lib/demo-storage";
import { aethexUserService, aethexAchievementService, type AethexUserProfile } from '@/lib/aethex-database-adapter'; import {
aethexUserService,
aethexAchievementService,
type AethexUserProfile,
} from "@/lib/aethex-database-adapter";
interface AuthContextType { interface AuthContextType {
user: User | null; user: User | null;
@ -12,8 +16,12 @@ interface AuthContextType {
session: Session | null; session: Session | null;
loading: boolean; loading: boolean;
signIn: (email: string, password: string) => Promise<void>; signIn: (email: string, password: string) => Promise<void>;
signUp: (email: string, password: string, userData?: Partial<AethexUserProfile>) => Promise<void>; signUp: (
signInWithOAuth: (provider: 'github' | 'google') => Promise<void>; email: string,
password: string,
userData?: Partial<AethexUserProfile>,
) => Promise<void>;
signInWithOAuth: (provider: "github" | "google") => Promise<void>;
signOut: () => Promise<void>; signOut: () => Promise<void>;
updateProfile: (updates: Partial<AethexUserProfile>) => Promise<void>; updateProfile: (updates: Partial<AethexUserProfile>) => Promise<void>;
} }
@ -23,12 +31,14 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const useAuth = () => { export const useAuth = () => {
const context = useContext(AuthContext); const context = useContext(AuthContext);
if (context === undefined) { 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; 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 [user, setUser] = useState<User | null>(null);
const [profile, setProfile] = useState<AethexUserProfile | null>(null); const [profile, setProfile] = useState<AethexUserProfile | null>(null);
const [session, setSession] = useState<Session | null>(null); const [session, setSession] = useState<Session | null>(null);
@ -37,23 +47,28 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
useEffect(() => { useEffect(() => {
// Check if Supabase is configured // Check if Supabase is configured
if (!isSupabaseConfigured) { 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); setLoading(false);
return; return;
} }
// Get initial session // Get initial session
supabase.auth.getSession().then(({ data: { session } }) => { supabase.auth
setSession(session); .getSession()
setUser(session?.user ?? null); .then(({ data: { session } }) => {
if (session?.user) { setSession(session);
fetchUserProfile(session.user.id); setUser(session?.user ?? null);
} if (session?.user) {
setLoading(false); fetchUserProfile(session.user.id);
}).catch((error) => { }
console.error('Error getting session:', error); setLoading(false);
setLoading(false); })
}); .catch((error) => {
console.error("Error getting session:", error);
setLoading(false);
});
// Listen for auth changes // Listen for auth changes
const { const {
@ -69,26 +84,30 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
if (!profile && session.user.app_metadata?.provider) { if (!profile && session.user.app_metadata?.provider) {
try { try {
await aethexUserService.createInitialProfile(session.user.id, { await aethexUserService.createInitialProfile(session.user.id, {
username: session.user.user_metadata?.user_name || username:
session.user.user_metadata?.preferred_username || session.user.user_metadata?.user_name ||
session.user.email?.split('@')[0] || session.user.user_metadata?.preferred_username ||
`user_${Date.now()}`, session.user.email?.split("@")[0] ||
full_name: session.user.user_metadata?.full_name || `user_${Date.now()}`,
session.user.user_metadata?.name || full_name:
session.user.email?.split('@')[0], session.user.user_metadata?.full_name ||
session.user.user_metadata?.name ||
session.user.email?.split("@")[0],
email: session.user.email, email: session.user.email,
avatar_url: session.user.user_metadata?.avatar_url, avatar_url: session.user.user_metadata?.avatar_url,
user_type: 'community_member', // Default for OAuth users user_type: "community_member", // Default for OAuth users
experience_level: 'beginner', experience_level: "beginner",
}); });
// Fetch the newly created profile // Fetch the newly created profile
await fetchUserProfile(session.user.id); await fetchUserProfile(session.user.id);
// Award onboarding achievement for OAuth users // Award onboarding achievement for OAuth users
await aethexAchievementService.checkAndAwardOnboardingAchievement(session.user.id); await aethexAchievementService.checkAndAwardOnboardingAchievement(
session.user.id,
);
} catch (error) { } catch (error) {
console.error('Error creating OAuth user profile:', error); console.error("Error creating OAuth user profile:", error);
} }
} }
} else { } else {
@ -97,15 +116,15 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setLoading(false); setLoading(false);
// Show toast notifications for auth events // Show toast notifications for auth events
if (event === 'SIGNED_IN') { if (event === "SIGNED_IN") {
aethexToast.success({ aethexToast.success({
title: 'Welcome back!', title: "Welcome back!",
description: 'Successfully signed in to AeThex OS' description: "Successfully signed in to AeThex OS",
}); });
} else if (event === 'SIGNED_OUT') { } else if (event === "SIGNED_OUT") {
aethexToast.info({ aethexToast.info({
title: 'Signed out', title: "Signed out",
description: 'Come back soon!' description: "Come back soon!",
}); });
} }
}); });
@ -113,7 +132,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
}, []); }, []);
const fetchUserProfile = async (userId: string): Promise<AethexUserProfile | null> => { const fetchUserProfile = async (
userId: string,
): Promise<AethexUserProfile | null> => {
if (!isSupabaseConfigured) { if (!isSupabaseConfigured) {
// Initialize demo data and get profile // Initialize demo data and get profile
DemoStorageService.initializeDemoData(); DemoStorageService.initializeDemoData();
@ -127,7 +148,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setProfile(userProfile); setProfile(userProfile);
return userProfile; return userProfile;
} catch (error) { } catch (error) {
console.error('Error fetching user profile:', error); console.error("Error fetching user profile:", error);
return null; return null;
} }
}; };
@ -135,13 +156,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const signIn = async (email: string, password: string) => { const signIn = async (email: string, password: string) => {
if (!isSupabaseConfigured) { if (!isSupabaseConfigured) {
aethexToast.warning({ aethexToast.warning({
title: 'Demo Mode', title: "Demo Mode",
description: 'Supabase not configured. This is a demo - please set up your Supabase project.' description:
"Supabase not configured. This is a demo - please set up your Supabase project.",
}); });
// Simulate successful login for demo // Simulate successful login for demo
setTimeout(() => { setTimeout(() => {
setUser({ id: 'demo-user', email } as User); setUser({ id: "demo-user", email } as User);
setSession({ user: { id: 'demo-user', email } } as Session); setSession({ user: { id: "demo-user", email } } as Session);
}, 500); }, 500);
return; return;
} }
@ -155,18 +177,23 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
if (error) throw error; if (error) throw error;
} catch (error: any) { } catch (error: any) {
aethexToast.error({ aethexToast.error({
title: 'Sign in failed', title: "Sign in failed",
description: error.message description: error.message,
}); });
throw error; throw error;
} }
}; };
const signUp = async (email: string, password: string, userData?: Partial<AethexUserProfile>) => { const signUp = async (
email: string,
password: string,
userData?: Partial<AethexUserProfile>,
) => {
if (!isSupabaseConfigured) { if (!isSupabaseConfigured) {
aethexToast.warning({ aethexToast.warning({
title: 'Demo Mode', title: "Demo Mode",
description: 'Supabase not configured. This is a demo - please set up your Supabase project.' description:
"Supabase not configured. This is a demo - please set up your Supabase project.",
}); });
return; return;
} }
@ -187,24 +214,25 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
}); });
aethexToast.success({ aethexToast.success({
title: 'Account created!', title: "Account created!",
description: 'Please check your email to verify your account' description: "Please check your email to verify your account",
}); });
} }
} catch (error: any) { } catch (error: any) {
aethexToast.error({ aethexToast.error({
title: 'Sign up failed', title: "Sign up failed",
description: error.message description: error.message,
}); });
throw error; throw error;
} }
}; };
const signInWithOAuth = async (provider: 'github' | 'google') => { const signInWithOAuth = async (provider: "github" | "google") => {
if (!isSupabaseConfigured) { if (!isSupabaseConfigured) {
aethexToast.warning({ aethexToast.warning({
title: 'Demo Mode', title: "Demo Mode",
description: 'OAuth login requires Supabase to be configured. Currently in demo mode.' description:
"OAuth login requires Supabase to be configured. Currently in demo mode.",
}); });
return; return;
} }
@ -220,13 +248,13 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
if (error) throw error; if (error) throw error;
aethexToast.success({ aethexToast.success({
title: 'Redirecting...', title: "Redirecting...",
description: `Signing in with ${provider}` description: `Signing in with ${provider}`,
}); });
} catch (error: any) { } catch (error: any) {
aethexToast.error({ aethexToast.error({
title: `${provider} sign in failed`, title: `${provider} sign in failed`,
description: error.message description: error.message,
}); });
throw error; throw error;
} }
@ -238,8 +266,8 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setSession(null); setSession(null);
setProfile(null); setProfile(null);
aethexToast.info({ aethexToast.info({
title: 'Signed out', title: "Signed out",
description: 'Demo session ended' description: "Demo session ended",
}); });
return; return;
} }
@ -249,38 +277,43 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
if (error) throw error; if (error) throw error;
} catch (error: any) { } catch (error: any) {
aethexToast.error({ aethexToast.error({
title: 'Sign out failed', title: "Sign out failed",
description: error.message description: error.message,
}); });
throw error; throw error;
} }
}; };
const updateProfile = async (updates: Partial<AethexUserProfile>) => { 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) { if (!isSupabaseConfigured) {
// Use demo storage // Use demo storage
const updatedProfile = DemoStorageService.updateUserProfile(updates as any); const updatedProfile = DemoStorageService.updateUserProfile(
updates as any,
);
setProfile(updatedProfile as AethexUserProfile); setProfile(updatedProfile as AethexUserProfile);
aethexToast.success({ aethexToast.success({
title: 'Profile updated', title: "Profile updated",
description: 'Your profile has been updated successfully' description: "Your profile has been updated successfully",
}); });
return; return;
} }
try { try {
const updatedProfile = await aethexUserService.updateProfile(user.id, updates); const updatedProfile = await aethexUserService.updateProfile(
user.id,
updates,
);
setProfile(updatedProfile); setProfile(updatedProfile);
aethexToast.success({ aethexToast.success({
title: 'Profile updated', title: "Profile updated",
description: 'Your profile has been updated successfully' description: "Your profile has been updated successfully",
}); });
} catch (error: any) { } catch (error: any) {
aethexToast.error({ aethexToast.error({
title: 'Update failed', title: "Update failed",
description: error.message description: error.message,
}); });
throw error; throw error;
} }
@ -298,9 +331,5 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
updateProfile, updateProfile,
}; };
return ( return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}; };

View file

@ -1,9 +1,9 @@
// Database adapter for existing AeThex community platform // Database adapter for existing AeThex community platform
// Maps existing schema to our application needs // Maps existing schema to our application needs
import { supabase } from './supabase'; import { supabase } from "./supabase";
import type { Database } from './database.types'; import type { Database } from "./database.types";
import { aethexToast } from './aethex-toast'; import { aethexToast } from "./aethex-toast";
// Extended user profile type that matches existing + new schema // Extended user profile type that matches existing + new schema
export interface AethexUserProfile { export interface AethexUserProfile {
@ -21,8 +21,8 @@ export interface AethexUserProfile {
created_at: string; created_at: string;
updated_at: string; updated_at: string;
// New AeThex app fields // New AeThex app fields
user_type?: 'game_developer' | 'client' | 'community_member' | 'customer'; user_type?: "game_developer" | "client" | "community_member" | "customer";
experience_level?: 'beginner' | 'intermediate' | 'advanced' | 'expert'; experience_level?: "beginner" | "intermediate" | "advanced" | "expert";
full_name?: string; full_name?: string;
location?: string; location?: string;
website_url?: string; website_url?: string;
@ -38,7 +38,7 @@ export interface AethexProject {
user_id: string; user_id: string;
title: string; title: string;
description?: string; description?: string;
status: 'planning' | 'in_progress' | 'completed' | 'on_hold'; status: "planning" | "in_progress" | "completed" | "on_hold";
technologies?: string[]; technologies?: string[];
github_url?: string; github_url?: string;
demo_url?: string; demo_url?: string;
@ -69,56 +69,64 @@ export interface AethexUserAchievement {
// User Profile Services // User Profile Services
export const aethexUserService = { export const aethexUserService = {
async getCurrentUser(): Promise<AethexUserProfile | null> { async getCurrentUser(): Promise<AethexUserProfile | null> {
const { data: { user } } = await supabase.auth.getUser(); const {
data: { user },
} = await supabase.auth.getUser();
if (!user) return null; if (!user) return null;
const { data, error } = await supabase const { data, error } = await supabase
.from('profiles') .from("profiles")
.select('*') .select("*")
.eq('id', user.id) .eq("id", user.id)
.single(); .single();
if (error) { if (error) {
console.error('Error fetching user profile:', error); console.error("Error fetching user profile:", error);
return null; return null;
} }
return data as AethexUserProfile; 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 const { data, error } = await supabase
.from('profiles') .from("profiles")
.update(updates) .update(updates)
.eq('id', userId) .eq("id", userId)
.select() .select()
.single(); .single();
if (error) { if (error) {
console.error('Error updating profile:', error); console.error("Error updating profile:", error);
throw error; throw error;
} }
return data as AethexUserProfile; 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 const { data, error } = await supabase
.from('profiles') .from("profiles")
.insert({ .insert({
id: userId, id: userId,
username: profileData.username || `user_${Date.now()}`, username: profileData.username || `user_${Date.now()}`,
user_type: profileData.user_type || 'community_member', user_type: profileData.user_type || "community_member",
experience_level: profileData.experience_level || 'beginner', experience_level: profileData.experience_level || "beginner",
full_name: profileData.full_name, full_name: profileData.full_name,
email: profileData.email, email: profileData.email,
...profileData ...profileData,
}) })
.select() .select()
.single(); .single();
if (error) { if (error) {
console.error('Error creating profile:', error); console.error("Error creating profile:", error);
throw error; throw error;
} }
@ -127,39 +135,36 @@ export const aethexUserService = {
async addUserInterests(userId: string, interests: string[]): Promise<void> { async addUserInterests(userId: string, interests: string[]): Promise<void> {
// First, delete existing interests // First, delete existing interests
await supabase await supabase.from("user_interests").delete().eq("user_id", userId);
.from('user_interests')
.delete()
.eq('user_id', userId);
// Insert new interests // Insert new interests
const interestRows = interests.map(interest => ({ const interestRows = interests.map((interest) => ({
user_id: userId, user_id: userId,
interest, interest,
})); }));
const { error } = await supabase const { error } = await supabase
.from('user_interests') .from("user_interests")
.insert(interestRows); .insert(interestRows);
if (error) { if (error) {
console.error('Error adding interests:', error); console.error("Error adding interests:", error);
throw error; throw error;
} }
}, },
async getUserInterests(userId: string): Promise<string[]> { async getUserInterests(userId: string): Promise<string[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('user_interests') .from("user_interests")
.select('interest') .select("interest")
.eq('user_id', userId); .eq("user_id", userId);
if (error) { if (error) {
console.error('Error fetching interests:', error); console.error("Error fetching interests:", error);
return []; return [];
} }
return data.map(item => item.interest); return data.map((item) => item.interest);
}, },
}; };
@ -167,44 +172,49 @@ export const aethexUserService = {
export const aethexProjectService = { export const aethexProjectService = {
async getUserProjects(userId: string): Promise<AethexProject[]> { async getUserProjects(userId: string): Promise<AethexProject[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('projects') .from("projects")
.select('*') .select("*")
.eq('user_id', userId) .eq("user_id", userId)
.order('created_at', { ascending: false }); .order("created_at", { ascending: false });
if (error) { if (error) {
console.error('Error fetching projects:', error); console.error("Error fetching projects:", error);
return []; return [];
} }
return data as AethexProject[]; 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 const { data, error } = await supabase
.from('projects') .from("projects")
.insert(project) .insert(project)
.select() .select()
.single(); .single();
if (error) { if (error) {
console.error('Error creating project:', error); console.error("Error creating project:", error);
throw error; throw error;
} }
return data as AethexProject; 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 const { data, error } = await supabase
.from('projects') .from("projects")
.update(updates) .update(updates)
.eq('id', projectId) .eq("id", projectId)
.select() .select()
.single(); .single();
if (error) { if (error) {
console.error('Error updating project:', error); console.error("Error updating project:", error);
throw error; throw error;
} }
@ -213,12 +223,12 @@ export const aethexProjectService = {
async deleteProject(projectId: string): Promise<boolean> { async deleteProject(projectId: string): Promise<boolean> {
const { error } = await supabase const { error } = await supabase
.from('projects') .from("projects")
.delete() .delete()
.eq('id', projectId); .eq("id", projectId);
if (error) { if (error) {
console.error('Error deleting project:', error); console.error("Error deleting project:", error);
return false; return false;
} }
@ -227,21 +237,23 @@ export const aethexProjectService = {
async getAllProjects(limit = 10): Promise<AethexProject[]> { async getAllProjects(limit = 10): Promise<AethexProject[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('projects') .from("projects")
.select(` .select(
`
*, *,
profiles!projects_user_id_fkey ( profiles!projects_user_id_fkey (
username, username,
full_name, full_name,
avatar_url avatar_url
) )
`) `,
.eq('status', 'completed') )
.order('created_at', { ascending: false }) .eq("status", "completed")
.order("created_at", { ascending: false })
.limit(limit); .limit(limit);
if (error) { if (error) {
console.error('Error fetching all projects:', error); console.error("Error fetching all projects:", error);
return []; return [];
} }
@ -253,12 +265,12 @@ export const aethexProjectService = {
export const aethexAchievementService = { export const aethexAchievementService = {
async getAllAchievements(): Promise<AethexAchievement[]> { async getAllAchievements(): Promise<AethexAchievement[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('achievements') .from("achievements")
.select('*') .select("*")
.order('points_reward', { ascending: false }); .order("points_reward", { ascending: false });
if (error) { if (error) {
console.error('Error fetching achievements:', error); console.error("Error fetching achievements:", error);
return []; return [];
} }
@ -267,45 +279,48 @@ export const aethexAchievementService = {
async getUserAchievements(userId: string): Promise<AethexAchievement[]> { async getUserAchievements(userId: string): Promise<AethexAchievement[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('user_achievements') .from("user_achievements")
.select(` .select(
`
unlocked_at, unlocked_at,
achievements (*) achievements (*)
`) `,
.eq('user_id', userId) )
.order('unlocked_at', { ascending: false }); .eq("user_id", userId)
.order("unlocked_at", { ascending: false });
if (error) { if (error) {
console.error('Error fetching user achievements:', error); console.error("Error fetching user achievements:", error);
return []; 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> { async awardAchievement(userId: string, achievementId: string): Promise<void> {
const { error } = await supabase const { error } = await supabase.from("user_achievements").insert({
.from('user_achievements') user_id: userId,
.insert({ achievement_id: achievementId,
user_id: userId, });
achievement_id: achievementId,
});
if (error && error.code !== '23505') { // Ignore duplicate key error if (error && error.code !== "23505") {
console.error('Error awarding achievement:', error); // Ignore duplicate key error
console.error("Error awarding achievement:", error);
throw error; throw error;
} }
// Get achievement details for toast // Get achievement details for toast
const { data: achievement } = await supabase const { data: achievement } = await supabase
.from('achievements') .from("achievements")
.select('*') .select("*")
.eq('id', achievementId) .eq("id", achievementId)
.single(); .single();
if (achievement) { if (achievement) {
aethexToast.aethex({ aethexToast.aethex({
title: 'Achievement Unlocked! 🎉', title: "Achievement Unlocked! 🎉",
description: `${achievement.icon} ${achievement.name} - ${achievement.description}`, description: `${achievement.icon} ${achievement.name} - ${achievement.description}`,
duration: 8000, duration: 8000,
}); });
@ -318,9 +333,9 @@ export const aethexAchievementService = {
async updateUserXPAndLevel(userId: string, xpGained: number): Promise<void> { async updateUserXPAndLevel(userId: string, xpGained: number): Promise<void> {
// Get current user data // Get current user data
const { data: profile } = await supabase const { data: profile } = await supabase
.from('profiles') .from("profiles")
.select('total_xp, level, loyalty_points') .select("total_xp, level, loyalty_points")
.eq('id', userId) .eq("id", userId)
.single(); .single();
if (!profile) return; if (!profile) return;
@ -331,23 +346,23 @@ export const aethexAchievementService = {
// Update profile // Update profile
await supabase await supabase
.from('profiles') .from("profiles")
.update({ .update({
total_xp: newTotalXP, total_xp: newTotalXP,
level: newLevel, level: newLevel,
loyalty_points: newLoyaltyPoints, loyalty_points: newLoyaltyPoints,
}) })
.eq('id', userId); .eq("id", userId);
// Check for level-up achievements // Check for level-up achievements
if (newLevel > (profile.level || 1)) { if (newLevel > (profile.level || 1)) {
if (newLevel >= 5) { if (newLevel >= 5) {
const levelUpAchievement = await supabase const levelUpAchievement = await supabase
.from('achievements') .from("achievements")
.select('id') .select("id")
.eq('name', 'Level Master') .eq("name", "Level Master")
.single(); .single();
if (levelUpAchievement.data) { if (levelUpAchievement.data) {
await this.awardAchievement(userId, levelUpAchievement.data.id); await this.awardAchievement(userId, levelUpAchievement.data.id);
} }
@ -357,9 +372,9 @@ export const aethexAchievementService = {
async checkAndAwardOnboardingAchievement(userId: string): Promise<void> { async checkAndAwardOnboardingAchievement(userId: string): Promise<void> {
const { data: achievement } = await supabase const { data: achievement } = await supabase
.from('achievements') .from("achievements")
.select('id') .select("id")
.eq('name', 'AeThex Explorer') .eq("name", "AeThex Explorer")
.single(); .single();
if (achievement) { if (achievement) {
@ -369,13 +384,13 @@ export const aethexAchievementService = {
async checkAndAwardProjectAchievements(userId: string): Promise<void> { async checkAndAwardProjectAchievements(userId: string): Promise<void> {
const projects = await aethexProjectService.getUserProjects(userId); const projects = await aethexProjectService.getUserProjects(userId);
// First project achievement // First project achievement
if (projects.length >= 1) { if (projects.length >= 1) {
const { data: achievement } = await supabase const { data: achievement } = await supabase
.from('achievements') .from("achievements")
.select('id') .select("id")
.eq('name', 'Portfolio Creator') .eq("name", "Portfolio Creator")
.single(); .single();
if (achievement) { if (achievement) {
@ -384,12 +399,12 @@ export const aethexAchievementService = {
} }
// Project master achievement // Project master achievement
const completedProjects = projects.filter(p => p.status === 'completed'); const completedProjects = projects.filter((p) => p.status === "completed");
if (completedProjects.length >= 10) { if (completedProjects.length >= 10) {
const { data: achievement } = await supabase const { data: achievement } = await supabase
.from('achievements') .from("achievements")
.select('id') .select("id")
.eq('name', 'Project Master') .eq("name", "Project Master")
.single(); .single();
if (achievement) { if (achievement) {
@ -403,14 +418,14 @@ export const aethexAchievementService = {
export const aethexNotificationService = { export const aethexNotificationService = {
async getUserNotifications(userId: string): Promise<any[]> { async getUserNotifications(userId: string): Promise<any[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('notifications') .from("notifications")
.select('*') .select("*")
.eq('user_id', userId) .eq("user_id", userId)
.order('created_at', { ascending: false }) .order("created_at", { ascending: false })
.limit(10); .limit(10);
if (error) { if (error) {
console.error('Error fetching notifications:', error); console.error("Error fetching notifications:", error);
return []; return [];
} }
@ -419,51 +434,56 @@ export const aethexNotificationService = {
async markAsRead(notificationId: string): Promise<void> { async markAsRead(notificationId: string): Promise<void> {
await supabase await supabase
.from('notifications') .from("notifications")
.update({ is_read: true }) .update({ is_read: true })
.eq('id', notificationId); .eq("id", notificationId);
}, },
async createNotification(userId: string, type: string, data: any): Promise<void> { async createNotification(
await supabase userId: string,
.from('notifications') type: string,
.insert({ data: any,
user_id: userId, ): Promise<void> {
type, await supabase.from("notifications").insert({
data, user_id: userId,
}); type,
data,
});
}, },
}; };
// Real-time subscriptions // Real-time subscriptions
export const aethexRealtimeService = { export const aethexRealtimeService = {
subscribeToUserNotifications(userId: string, callback: (notification: any) => void) { subscribeToUserNotifications(
userId: string,
callback: (notification: any) => void,
) {
return supabase return supabase
.channel(`notifications:${userId}`) .channel(`notifications:${userId}`)
.on( .on(
'postgres_changes', "postgres_changes",
{ {
event: 'INSERT', event: "INSERT",
schema: 'public', schema: "public",
table: 'notifications', table: "notifications",
filter: `user_id=eq.${userId}`, filter: `user_id=eq.${userId}`,
}, },
callback callback,
) )
.subscribe(); .subscribe();
}, },
subscribeToProjects(callback: (project: any) => void) { subscribeToProjects(callback: (project: any) => void) {
return supabase return supabase
.channel('projects') .channel("projects")
.on( .on(
'postgres_changes', "postgres_changes",
{ {
event: 'INSERT', event: "INSERT",
schema: 'public', schema: "public",
table: 'projects', table: "projects",
}, },
callback callback,
) )
.subscribe(); .subscribe();
}, },

View file

@ -4,378 +4,394 @@ export type Json =
| boolean | boolean
| null | null
| { [key: string]: Json | undefined } | { [key: string]: Json | undefined }
| Json[] | Json[];
export type Database = { export type Database = {
public: { public: {
Tables: { Tables: {
achievements: { achievements: {
Row: { Row: {
badge_color: string | null badge_color: string | null;
created_at: string created_at: string;
description: string | null description: string | null;
icon: string | null icon: string | null;
id: string id: string;
name: string name: string;
xp_reward: number | null xp_reward: number | null;
} };
Insert: { Insert: {
badge_color?: string | null badge_color?: string | null;
created_at?: string created_at?: string;
description?: string | null description?: string | null;
icon?: string | null icon?: string | null;
id?: string id?: string;
name: string name: string;
xp_reward?: number | null xp_reward?: number | null;
} };
Update: { Update: {
badge_color?: string | null badge_color?: string | null;
created_at?: string created_at?: string;
description?: string | null description?: string | null;
icon?: string | null icon?: string | null;
id?: string id?: string;
name?: string name?: string;
xp_reward?: number | null xp_reward?: number | null;
} };
Relationships: [] Relationships: [];
} };
comments: { comments: {
Row: { Row: {
author_id: string author_id: string;
content: string content: string;
created_at: string created_at: string;
id: string id: string;
post_id: string post_id: string;
} };
Insert: { Insert: {
author_id: string author_id: string;
content: string content: string;
created_at?: string created_at?: string;
id?: string id?: string;
post_id: string post_id: string;
} };
Update: { Update: {
author_id?: string author_id?: string;
content?: string content?: string;
created_at?: string created_at?: string;
id?: string id?: string;
post_id?: string post_id?: string;
} };
Relationships: [ Relationships: [
{ {
foreignKeyName: "comments_author_id_fkey" foreignKeyName: "comments_author_id_fkey";
columns: ["author_id"] columns: ["author_id"];
isOneToOne: false isOneToOne: false;
referencedRelation: "user_profiles" referencedRelation: "user_profiles";
referencedColumns: ["id"] referencedColumns: ["id"];
}, },
{ {
foreignKeyName: "comments_post_id_fkey" foreignKeyName: "comments_post_id_fkey";
columns: ["post_id"] columns: ["post_id"];
isOneToOne: false isOneToOne: false;
referencedRelation: "community_posts" referencedRelation: "community_posts";
referencedColumns: ["id"] referencedColumns: ["id"];
} },
] ];
} };
community_posts: { community_posts: {
Row: { Row: {
author_id: string author_id: string;
category: string | null category: string | null;
comments_count: number | null comments_count: number | null;
content: string content: string;
created_at: string created_at: string;
id: string id: string;
is_published: boolean | null is_published: boolean | null;
likes_count: number | null likes_count: number | null;
tags: string[] | null tags: string[] | null;
title: string title: string;
updated_at: string updated_at: string;
} };
Insert: { Insert: {
author_id: string author_id: string;
category?: string | null category?: string | null;
comments_count?: number | null comments_count?: number | null;
content: string content: string;
created_at?: string created_at?: string;
id?: string id?: string;
is_published?: boolean | null is_published?: boolean | null;
likes_count?: number | null likes_count?: number | null;
tags?: string[] | null tags?: string[] | null;
title: string title: string;
updated_at?: string updated_at?: string;
} };
Update: { Update: {
author_id?: string author_id?: string;
category?: string | null category?: string | null;
comments_count?: number | null comments_count?: number | null;
content?: string content?: string;
created_at?: string created_at?: string;
id?: string id?: string;
is_published?: boolean | null is_published?: boolean | null;
likes_count?: number | null likes_count?: number | null;
tags?: string[] | null tags?: string[] | null;
title?: string title?: string;
updated_at?: string updated_at?: string;
} };
Relationships: [ Relationships: [
{ {
foreignKeyName: "community_posts_author_id_fkey" foreignKeyName: "community_posts_author_id_fkey";
columns: ["author_id"] columns: ["author_id"];
isOneToOne: false isOneToOne: false;
referencedRelation: "user_profiles" referencedRelation: "user_profiles";
referencedColumns: ["id"] referencedColumns: ["id"];
} },
] ];
} };
notifications: { notifications: {
Row: { Row: {
created_at: string created_at: string;
id: string id: string;
message: string | null message: string | null;
read: boolean | null read: boolean | null;
title: string title: string;
type: string | null type: string | null;
user_id: string user_id: string;
} };
Insert: { Insert: {
created_at?: string created_at?: string;
id?: string id?: string;
message?: string | null message?: string | null;
read?: boolean | null read?: boolean | null;
title: string title: string;
type?: string | null type?: string | null;
user_id: string user_id: string;
} };
Update: { Update: {
created_at?: string created_at?: string;
id?: string id?: string;
message?: string | null message?: string | null;
read?: boolean | null read?: boolean | null;
title?: string title?: string;
type?: string | null type?: string | null;
user_id?: string user_id?: string;
} };
Relationships: [ Relationships: [
{ {
foreignKeyName: "notifications_user_id_fkey" foreignKeyName: "notifications_user_id_fkey";
columns: ["user_id"] columns: ["user_id"];
isOneToOne: false isOneToOne: false;
referencedRelation: "user_profiles" referencedRelation: "user_profiles";
referencedColumns: ["id"] referencedColumns: ["id"];
} },
] ];
} };
projects: { projects: {
Row: { Row: {
created_at: string created_at: string;
demo_url: string | null demo_url: string | null;
description: string | null description: string | null;
end_date: string | null end_date: string | null;
github_url: string | null github_url: string | null;
id: string id: string;
image_url: string | null image_url: string | null;
start_date: string | null start_date: string | null;
status: Database["public"]["Enums"]["project_status_enum"] | null status: Database["public"]["Enums"]["project_status_enum"] | null;
technologies: string[] | null technologies: string[] | null;
title: string title: string;
updated_at: string updated_at: string;
user_id: string user_id: string;
} };
Insert: { Insert: {
created_at?: string created_at?: string;
demo_url?: string | null demo_url?: string | null;
description?: string | null description?: string | null;
end_date?: string | null end_date?: string | null;
github_url?: string | null github_url?: string | null;
id?: string id?: string;
image_url?: string | null image_url?: string | null;
start_date?: string | null start_date?: string | null;
status?: Database["public"]["Enums"]["project_status_enum"] | null status?: Database["public"]["Enums"]["project_status_enum"] | null;
technologies?: string[] | null technologies?: string[] | null;
title: string title: string;
updated_at?: string updated_at?: string;
user_id: string user_id: string;
} };
Update: { Update: {
created_at?: string created_at?: string;
demo_url?: string | null demo_url?: string | null;
description?: string | null description?: string | null;
end_date?: string | null end_date?: string | null;
github_url?: string | null github_url?: string | null;
id?: string id?: string;
image_url?: string | null image_url?: string | null;
start_date?: string | null start_date?: string | null;
status?: Database["public"]["Enums"]["project_status_enum"] | null status?: Database["public"]["Enums"]["project_status_enum"] | null;
technologies?: string[] | null technologies?: string[] | null;
title?: string title?: string;
updated_at?: string updated_at?: string;
user_id?: string user_id?: string;
} };
Relationships: [ Relationships: [
{ {
foreignKeyName: "projects_user_id_fkey" foreignKeyName: "projects_user_id_fkey";
columns: ["user_id"] columns: ["user_id"];
isOneToOne: false isOneToOne: false;
referencedRelation: "user_profiles" referencedRelation: "user_profiles";
referencedColumns: ["id"] referencedColumns: ["id"];
} },
] ];
} };
user_achievements: { user_achievements: {
Row: { Row: {
achievement_id: string achievement_id: string;
earned_at: string earned_at: string;
id: string id: string;
user_id: string user_id: string;
} };
Insert: { Insert: {
achievement_id: string achievement_id: string;
earned_at?: string earned_at?: string;
id?: string id?: string;
user_id: string user_id: string;
} };
Update: { Update: {
achievement_id?: string achievement_id?: string;
earned_at?: string earned_at?: string;
id?: string id?: string;
user_id?: string user_id?: string;
} };
Relationships: [ Relationships: [
{ {
foreignKeyName: "user_achievements_achievement_id_fkey" foreignKeyName: "user_achievements_achievement_id_fkey";
columns: ["achievement_id"] columns: ["achievement_id"];
isOneToOne: false isOneToOne: false;
referencedRelation: "achievements" referencedRelation: "achievements";
referencedColumns: ["id"] referencedColumns: ["id"];
}, },
{ {
foreignKeyName: "user_achievements_user_id_fkey" foreignKeyName: "user_achievements_user_id_fkey";
columns: ["user_id"] columns: ["user_id"];
isOneToOne: false isOneToOne: false;
referencedRelation: "user_profiles" referencedRelation: "user_profiles";
referencedColumns: ["id"] referencedColumns: ["id"];
} },
] ];
} };
user_interests: { user_interests: {
Row: { Row: {
created_at: string created_at: string;
id: string id: string;
interest: string interest: string;
user_id: string user_id: string;
} };
Insert: { Insert: {
created_at?: string created_at?: string;
id?: string id?: string;
interest: string interest: string;
user_id: string user_id: string;
} };
Update: { Update: {
created_at?: string created_at?: string;
id?: string id?: string;
interest?: string interest?: string;
user_id?: string user_id?: string;
} };
Relationships: [ Relationships: [
{ {
foreignKeyName: "user_interests_user_id_fkey" foreignKeyName: "user_interests_user_id_fkey";
columns: ["user_id"] columns: ["user_id"];
isOneToOne: false isOneToOne: false;
referencedRelation: "user_profiles" referencedRelation: "user_profiles";
referencedColumns: ["id"] referencedColumns: ["id"];
} },
] ];
} };
user_profiles: { user_profiles: {
Row: { Row: {
avatar_url: string | null avatar_url: string | null;
bio: string | null bio: string | null;
created_at: string created_at: string;
experience_level: Database["public"]["Enums"]["experience_level_enum"] | null experience_level:
full_name: string | null | Database["public"]["Enums"]["experience_level_enum"]
github_url: string | null | null;
id: string full_name: string | null;
level: number | null github_url: string | null;
linkedin_url: string | null id: string;
location: string | null level: number | null;
total_xp: number | null linkedin_url: string | null;
twitter_url: string | null location: string | null;
updated_at: string total_xp: number | null;
user_type: Database["public"]["Enums"]["user_type_enum"] twitter_url: string | null;
username: string | null updated_at: string;
website_url: string | null user_type: Database["public"]["Enums"]["user_type_enum"];
} username: string | null;
website_url: string | null;
};
Insert: { Insert: {
avatar_url?: string | null avatar_url?: string | null;
bio?: string | null bio?: string | null;
created_at?: string created_at?: string;
experience_level?: Database["public"]["Enums"]["experience_level_enum"] | null experience_level?:
full_name?: string | null | Database["public"]["Enums"]["experience_level_enum"]
github_url?: string | null | null;
id: string full_name?: string | null;
level?: number | null github_url?: string | null;
linkedin_url?: string | null id: string;
location?: string | null level?: number | null;
total_xp?: number | null linkedin_url?: string | null;
twitter_url?: string | null location?: string | null;
updated_at?: string total_xp?: number | null;
user_type: Database["public"]["Enums"]["user_type_enum"] twitter_url?: string | null;
username?: string | null updated_at?: string;
website_url?: string | null user_type: Database["public"]["Enums"]["user_type_enum"];
} username?: string | null;
website_url?: string | null;
};
Update: { Update: {
avatar_url?: string | null avatar_url?: string | null;
bio?: string | null bio?: string | null;
created_at?: string created_at?: string;
experience_level?: Database["public"]["Enums"]["experience_level_enum"] | null experience_level?:
full_name?: string | null | Database["public"]["Enums"]["experience_level_enum"]
github_url?: string | null | null;
id?: string full_name?: string | null;
level?: number | null github_url?: string | null;
linkedin_url?: string | null id?: string;
location?: string | null level?: number | null;
total_xp?: number | null linkedin_url?: string | null;
twitter_url?: string | null location?: string | null;
updated_at?: string total_xp?: number | null;
user_type?: Database["public"]["Enums"]["user_type_enum"] twitter_url?: string | null;
username?: string | null updated_at?: string;
website_url?: string | null user_type?: Database["public"]["Enums"]["user_type_enum"];
} username?: string | null;
website_url?: string | null;
};
Relationships: [ Relationships: [
{ {
foreignKeyName: "user_profiles_id_fkey" foreignKeyName: "user_profiles_id_fkey";
columns: ["id"] columns: ["id"];
isOneToOne: true isOneToOne: true;
referencedRelation: "users" referencedRelation: "users";
referencedColumns: ["id"] referencedColumns: ["id"];
} },
] ];
} };
} };
Views: { Views: {
[_ in never]: never [_ in never]: never;
} };
Functions: { Functions: {
[_ in never]: never [_ in never]: never;
} };
Enums: { Enums: {
experience_level_enum: "beginner" | "intermediate" | "advanced" | "expert" experience_level_enum:
project_status_enum: "planning" | "in_progress" | "completed" | "on_hold" | "beginner"
user_type_enum: "game_developer" | "client" | "community_member" | "customer" | "intermediate"
} | "advanced"
| "expert";
project_status_enum: "planning" | "in_progress" | "completed" | "on_hold";
user_type_enum:
| "game_developer"
| "client"
| "community_member"
| "customer";
};
CompositeTypes: { CompositeTypes: {
[_ in never]: never [_ in never]: never;
} };
} };
} };
export type UserProfile = Database['public']['Tables']['user_profiles']['Row']; export type UserProfile = Database["public"]["Tables"]["user_profiles"]["Row"];
export type Project = Database['public']['Tables']['projects']['Row']; export type Project = Database["public"]["Tables"]["projects"]["Row"];
export type Achievement = Database['public']['Tables']['achievements']['Row']; export type Achievement = Database["public"]["Tables"]["achievements"]["Row"];
export type CommunityPost = Database['public']['Tables']['community_posts']['Row']; export type CommunityPost =
export type Notification = Database['public']['Tables']['notifications']['Row']; 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 UserType = Database["public"]["Enums"]["user_type_enum"];
export type ExperienceLevel = Database['public']['Enums']['experience_level_enum']; export type ExperienceLevel =
export type ProjectStatus = Database['public']['Enums']['project_status_enum']; 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 // Demo storage service - simulates backend functionality using localStorage
import type { UserProfile, Project, Achievement } from './database.types'; import type { UserProfile, Project, Achievement } from "./database.types";
import { aethexToast } from './aethex-toast'; import { aethexToast } from "./aethex-toast";
const STORAGE_KEYS = { const STORAGE_KEYS = {
USER_PROFILE: 'aethex_demo_user_profile', USER_PROFILE: "aethex_demo_user_profile",
PROJECTS: 'aethex_demo_projects', PROJECTS: "aethex_demo_projects",
ACHIEVEMENTS: 'aethex_demo_achievements', ACHIEVEMENTS: "aethex_demo_achievements",
NOTIFICATIONS: 'aethex_demo_notifications', NOTIFICATIONS: "aethex_demo_notifications",
INTERESTS: 'aethex_demo_interests', INTERESTS: "aethex_demo_interests",
}; };
// Demo user data // Demo user data
const DEMO_USER_PROFILE: Partial<UserProfile> = { const DEMO_USER_PROFILE: Partial<UserProfile> = {
id: 'demo-user-123', id: "demo-user-123",
username: 'demo_developer', username: "demo_developer",
full_name: 'Demo Developer', full_name: "Demo Developer",
user_type: 'game_developer', user_type: "game_developer",
experience_level: 'intermediate', experience_level: "intermediate",
total_xp: 1250, total_xp: 1250,
level: 5, level: 5,
bio: 'Passionate game developer exploring the AeThex ecosystem', bio: "Passionate game developer exploring the AeThex ecosystem",
location: 'Digital Realm', location: "Digital Realm",
github_url: 'https://github.com/demo-developer', github_url: "https://github.com/demo-developer",
twitter_url: 'https://twitter.com/demo_dev', twitter_url: "https://twitter.com/demo_dev",
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
}; };
const DEMO_PROJECTS: Project[] = [ const DEMO_PROJECTS: Project[] = [
{ {
id: 'proj-1', id: "proj-1",
user_id: 'demo-user-123', user_id: "demo-user-123",
title: 'Quantum Quest', title: "Quantum Quest",
description: 'A sci-fi adventure game built with AeThex Engine', description: "A sci-fi adventure game built with AeThex Engine",
status: 'in_progress', status: "in_progress",
technologies: ['AeThex Engine', 'TypeScript', 'WebGL'], technologies: ["AeThex Engine", "TypeScript", "WebGL"],
github_url: 'https://github.com/demo/quantum-quest', github_url: "https://github.com/demo/quantum-quest",
demo_url: 'https://quantum-quest-demo.com', demo_url: "https://quantum-quest-demo.com",
image_url: null, image_url: null,
start_date: '2024-01-15', start_date: "2024-01-15",
end_date: null, end_date: null,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
}, },
{ {
id: 'proj-2', id: "proj-2",
user_id: 'demo-user-123', user_id: "demo-user-123",
title: 'Neon Runner', title: "Neon Runner",
description: 'Fast-paced endless runner with cyberpunk aesthetics', description: "Fast-paced endless runner with cyberpunk aesthetics",
status: 'completed', status: "completed",
technologies: ['AeThex Engine', 'JavaScript', 'CSS3'], technologies: ["AeThex Engine", "JavaScript", "CSS3"],
github_url: 'https://github.com/demo/neon-runner', github_url: "https://github.com/demo/neon-runner",
demo_url: 'https://neon-runner-demo.com', demo_url: "https://neon-runner-demo.com",
image_url: null, image_url: null,
start_date: '2023-08-01', start_date: "2023-08-01",
end_date: '2023-12-15', end_date: "2023-12-15",
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
}, },
{ {
id: 'proj-3', id: "proj-3",
user_id: 'demo-user-123', user_id: "demo-user-123",
title: 'Pixel Physics', title: "Pixel Physics",
description: 'Educational physics simulation game', description: "Educational physics simulation game",
status: 'planning', status: "planning",
technologies: ['AeThex Engine', 'React', 'Physics Engine'], technologies: ["AeThex Engine", "React", "Physics Engine"],
github_url: null, github_url: null,
demo_url: null, demo_url: null,
image_url: null, image_url: null,
start_date: '2024-03-01', start_date: "2024-03-01",
end_date: null, end_date: null,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
@ -77,39 +77,39 @@ const DEMO_PROJECTS: Project[] = [
const DEMO_ACHIEVEMENTS: Achievement[] = [ const DEMO_ACHIEVEMENTS: Achievement[] = [
{ {
id: 'ach-1', id: "ach-1",
name: 'Welcome to AeThex', name: "Welcome to AeThex",
description: 'Complete your profile setup', description: "Complete your profile setup",
icon: '🎉', icon: "🎉",
xp_reward: 100, xp_reward: 100,
badge_color: '#10B981', badge_color: "#10B981",
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
}, },
{ {
id: 'ach-2', id: "ach-2",
name: 'First Project', name: "First Project",
description: 'Create your first project', description: "Create your first project",
icon: '🚀', icon: "🚀",
xp_reward: 150, xp_reward: 150,
badge_color: '#3B82F6', badge_color: "#3B82F6",
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
}, },
{ {
id: 'ach-3', id: "ach-3",
name: 'Community Contributor', name: "Community Contributor",
description: 'Make your first community post', description: "Make your first community post",
icon: '💬', icon: "💬",
xp_reward: 75, xp_reward: 75,
badge_color: '#8B5CF6', badge_color: "#8B5CF6",
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
}, },
{ {
id: 'ach-4', id: "ach-4",
name: 'Experienced Developer', name: "Experienced Developer",
description: 'Complete 5 projects', description: "Complete 5 projects",
icon: '👨‍💻', icon: "👨‍💻",
xp_reward: 300, xp_reward: 300,
badge_color: '#EF4444', badge_color: "#EF4444",
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
}, },
]; ];
@ -123,7 +123,11 @@ export class DemoStorageService {
static updateUserProfile(updates: Partial<UserProfile>): UserProfile { static updateUserProfile(updates: Partial<UserProfile>): UserProfile {
const current = this.getUserProfile() || DEMO_USER_PROFILE; 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)); localStorage.setItem(STORAGE_KEYS.USER_PROFILE, JSON.stringify(updated));
return updated as UserProfile; return updated as UserProfile;
} }
@ -134,7 +138,9 @@ export class DemoStorageService {
return stored ? JSON.parse(stored) : DEMO_PROJECTS; 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 projects = this.getUserProjects();
const newProject: Project = { const newProject: Project = {
...project, ...project,
@ -147,19 +153,26 @@ export class DemoStorageService {
return newProject; 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 projects = this.getUserProjects();
const index = projects.findIndex(p => p.id === projectId); const index = projects.findIndex((p) => p.id === projectId);
if (index === -1) return null; 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)); localStorage.setItem(STORAGE_KEYS.PROJECTS, JSON.stringify(projects));
return projects[index]; return projects[index];
} }
static deleteProject(projectId: string): boolean { static deleteProject(projectId: string): boolean {
const projects = this.getUserProjects(); 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)); localStorage.setItem(STORAGE_KEYS.PROJECTS, JSON.stringify(filtered));
return filtered.length < projects.length; return filtered.length < projects.length;
} }
@ -171,22 +184,25 @@ export class DemoStorageService {
static getUserAchievements(): Achievement[] { static getUserAchievements(): Achievement[] {
const stored = localStorage.getItem(STORAGE_KEYS.ACHIEVEMENTS); 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"];
return DEMO_ACHIEVEMENTS.filter(ach => earnedIds.includes(ach.id)); return DEMO_ACHIEVEMENTS.filter((ach) => earnedIds.includes(ach.id));
} }
static awardAchievement(achievementId: string): void { static awardAchievement(achievementId: string): void {
const stored = localStorage.getItem(STORAGE_KEYS.ACHIEVEMENTS); 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)) { if (!earnedIds.includes(achievementId)) {
earnedIds.push(achievementId); earnedIds.push(achievementId);
localStorage.setItem(STORAGE_KEYS.ACHIEVEMENTS, JSON.stringify(earnedIds)); localStorage.setItem(
STORAGE_KEYS.ACHIEVEMENTS,
const achievement = DEMO_ACHIEVEMENTS.find(a => a.id === achievementId); JSON.stringify(earnedIds),
);
const achievement = DEMO_ACHIEVEMENTS.find((a) => a.id === achievementId);
if (achievement) { if (achievement) {
aethexToast.aethex({ aethexToast.aethex({
title: 'Achievement Unlocked!', title: "Achievement Unlocked!",
description: `${achievement.icon} ${achievement.name} - ${achievement.description}`, description: `${achievement.icon} ${achievement.name} - ${achievement.description}`,
duration: 8000, duration: 8000,
}); });
@ -197,7 +213,9 @@ export class DemoStorageService {
// Interests Management // Interests Management
static getUserInterests(): string[] { static getUserInterests(): string[] {
const stored = localStorage.getItem(STORAGE_KEYS.INTERESTS); 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 { static updateUserInterests(interests: string[]): void {
@ -208,22 +226,34 @@ export class DemoStorageService {
static initializeDemoData(): void { static initializeDemoData(): void {
// Only initialize if no data exists // Only initialize if no data exists
if (!localStorage.getItem(STORAGE_KEYS.USER_PROFILE)) { 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)) { 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)) { 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)) { 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 // Clear all demo data
static clearDemoData(): void { static clearDemoData(): void {
Object.values(STORAGE_KEYS).forEach(key => { Object.values(STORAGE_KEYS).forEach((key) => {
localStorage.removeItem(key); localStorage.removeItem(key);
}); });
} }

View file

@ -1,27 +1,36 @@
import { supabase } from './supabase'; import { supabase } from "./supabase";
import type { Database, UserProfile, Project, Achievement, CommunityPost } from './database.types'; import type {
Database,
UserProfile,
Project,
Achievement,
CommunityPost,
} from "./database.types";
// User Profile Services // User Profile Services
export const userProfileService = { export const userProfileService = {
async getProfile(userId: string): Promise<UserProfile | null> { async getProfile(userId: string): Promise<UserProfile | null> {
const { data, error } = await supabase const { data, error } = await supabase
.from('user_profiles') .from("user_profiles")
.select('*') .select("*")
.eq('id', userId) .eq("id", userId)
.single(); .single();
if (error && error.code !== 'PGRST116') { if (error && error.code !== "PGRST116") {
throw error; throw error;
} }
return data; 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 const { data, error } = await supabase
.from('user_profiles') .from("user_profiles")
.update(updates) .update(updates)
.eq('id', userId) .eq("id", userId)
.select() .select()
.single(); .single();
@ -29,9 +38,11 @@ export const userProfileService = {
return data; 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 const { data, error } = await supabase
.from('user_profiles') .from("user_profiles")
.insert(profile) .insert(profile)
.select() .select()
.single(); .single();
@ -41,13 +52,13 @@ export const userProfileService = {
}, },
async addInterests(userId: string, interests: string[]): Promise<void> { async addInterests(userId: string, interests: string[]): Promise<void> {
const interestRows = interests.map(interest => ({ const interestRows = interests.map((interest) => ({
user_id: userId, user_id: userId,
interest, interest,
})); }));
const { error } = await supabase const { error } = await supabase
.from('user_interests') .from("user_interests")
.insert(interestRows); .insert(interestRows);
if (error) throw error; if (error) throw error;
@ -55,12 +66,12 @@ export const userProfileService = {
async getUserInterests(userId: string): Promise<string[]> { async getUserInterests(userId: string): Promise<string[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('user_interests') .from("user_interests")
.select('interest') .select("interest")
.eq('user_id', userId); .eq("user_id", userId);
if (error) throw error; 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 = { export const projectService = {
async getUserProjects(userId: string): Promise<Project[]> { async getUserProjects(userId: string): Promise<Project[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('projects') .from("projects")
.select('*') .select("*")
.eq('user_id', userId) .eq("user_id", userId)
.order('created_at', { ascending: false }); .order("created_at", { ascending: false });
if (error) throw error; if (error) throw error;
return data; 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 const { data, error } = await supabase
.from('projects') .from("projects")
.insert(project) .insert(project)
.select() .select()
.single(); .single();
@ -88,11 +101,14 @@ export const projectService = {
return data; 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 const { data, error } = await supabase
.from('projects') .from("projects")
.update(updates) .update(updates)
.eq('id', projectId) .eq("id", projectId)
.select() .select()
.single(); .single();
@ -102,26 +118,28 @@ export const projectService = {
async deleteProject(projectId: string): Promise<void> { async deleteProject(projectId: string): Promise<void> {
const { error } = await supabase const { error } = await supabase
.from('projects') .from("projects")
.delete() .delete()
.eq('id', projectId); .eq("id", projectId);
if (error) throw error; if (error) throw error;
}, },
async getAllProjects(limit = 10): Promise<Project[]> { async getAllProjects(limit = 10): Promise<Project[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('projects') .from("projects")
.select(` .select(
`
*, *,
user_profiles ( user_profiles (
username, username,
full_name, full_name,
avatar_url avatar_url
) )
`) `,
.eq('status', 'completed') )
.order('created_at', { ascending: false }) .eq("status", "completed")
.order("created_at", { ascending: false })
.limit(limit); .limit(limit);
if (error) throw error; if (error) throw error;
@ -133,9 +151,9 @@ export const projectService = {
export const achievementService = { export const achievementService = {
async getAllAchievements(): Promise<Achievement[]> { async getAllAchievements(): Promise<Achievement[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('achievements') .from("achievements")
.select('*') .select("*")
.order('xp_reward', { ascending: false }); .order("xp_reward", { ascending: false });
if (error) throw error; if (error) throw error;
return data; return data;
@ -143,27 +161,30 @@ export const achievementService = {
async getUserAchievements(userId: string): Promise<Achievement[]> { async getUserAchievements(userId: string): Promise<Achievement[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('user_achievements') .from("user_achievements")
.select(` .select(
`
earned_at, earned_at,
achievements (*) achievements (*)
`) `,
.eq('user_id', userId) )
.order('earned_at', { ascending: false }); .eq("user_id", userId)
.order("earned_at", { ascending: false });
if (error) throw error; 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> { async awardAchievement(userId: string, achievementId: string): Promise<void> {
const { error } = await supabase const { error } = await supabase.from("user_achievements").insert({
.from('user_achievements') user_id: userId,
.insert({ achievement_id: achievementId,
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; throw error;
} }
}, },
@ -172,14 +193,16 @@ export const achievementService = {
// Check for various achievement conditions // Check for various achievement conditions
const profile = await userProfileService.getProfile(userId); const profile = await userProfileService.getProfile(userId);
const projects = await projectService.getUserProjects(userId); const projects = await projectService.getUserProjects(userId);
if (!profile) return; if (!profile) return;
const achievements = await this.getAllAchievements(); const achievements = await this.getAllAchievements();
// Welcome achievement // Welcome achievement
if (profile.full_name && profile.user_type) { 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) { if (welcomeAchievement) {
await this.awardAchievement(userId, welcomeAchievement.id); await this.awardAchievement(userId, welcomeAchievement.id);
} }
@ -187,16 +210,20 @@ export const achievementService = {
// First project achievement // First project achievement
if (projects.length >= 1) { if (projects.length >= 1) {
const firstProjectAchievement = achievements.find(a => a.name === 'First Project'); const firstProjectAchievement = achievements.find(
(a) => a.name === "First Project",
);
if (firstProjectAchievement) { if (firstProjectAchievement) {
await this.awardAchievement(userId, firstProjectAchievement.id); await this.awardAchievement(userId, firstProjectAchievement.id);
} }
} }
// Experienced developer achievement // Experienced developer achievement
const completedProjects = projects.filter(p => p.status === 'completed'); const completedProjects = projects.filter((p) => p.status === "completed");
if (completedProjects.length >= 5) { if (completedProjects.length >= 5) {
const experiencedAchievement = achievements.find(a => a.name === 'Experienced Developer'); const experiencedAchievement = achievements.find(
(a) => a.name === "Experienced Developer",
);
if (experiencedAchievement) { if (experiencedAchievement) {
await this.awardAchievement(userId, experiencedAchievement.id); await this.awardAchievement(userId, experiencedAchievement.id);
} }
@ -208,26 +235,33 @@ export const achievementService = {
export const communityService = { export const communityService = {
async getPosts(limit = 10): Promise<CommunityPost[]> { async getPosts(limit = 10): Promise<CommunityPost[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('community_posts') .from("community_posts")
.select(` .select(
`
*, *,
user_profiles ( user_profiles (
username, username,
full_name, full_name,
avatar_url avatar_url
) )
`) `,
.eq('is_published', true) )
.order('created_at', { ascending: false }) .eq("is_published", true)
.order("created_at", { ascending: false })
.limit(limit); .limit(limit);
if (error) throw error; if (error) throw error;
return data; 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 const { data, error } = await supabase
.from('community_posts') .from("community_posts")
.insert(post) .insert(post)
.select() .select()
.single(); .single();
@ -238,10 +272,10 @@ export const communityService = {
async getUserPosts(userId: string): Promise<CommunityPost[]> { async getUserPosts(userId: string): Promise<CommunityPost[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('community_posts') .from("community_posts")
.select('*') .select("*")
.eq('author_id', userId) .eq("author_id", userId)
.order('created_at', { ascending: false }); .order("created_at", { ascending: false });
if (error) throw error; if (error) throw error;
return data; return data;
@ -252,10 +286,10 @@ export const communityService = {
export const notificationService = { export const notificationService = {
async getUserNotifications(userId: string): Promise<any[]> { async getUserNotifications(userId: string): Promise<any[]> {
const { data, error } = await supabase const { data, error } = await supabase
.from('notifications') .from("notifications")
.select('*') .select("*")
.eq('user_id', userId) .eq("user_id", userId)
.order('created_at', { ascending: false }) .order("created_at", { ascending: false })
.limit(10); .limit(10);
if (error) throw error; if (error) throw error;
@ -264,22 +298,25 @@ export const notificationService = {
async markAsRead(notificationId: string): Promise<void> { async markAsRead(notificationId: string): Promise<void> {
const { error } = await supabase const { error } = await supabase
.from('notifications') .from("notifications")
.update({ read: true }) .update({ read: true })
.eq('id', notificationId); .eq("id", notificationId);
if (error) throw error; if (error) throw error;
}, },
async createNotification(userId: string, title: string, message?: string, type = 'info'): Promise<void> { async createNotification(
const { error } = await supabase userId: string,
.from('notifications') title: string,
.insert({ message?: string,
user_id: userId, type = "info",
title, ): Promise<void> {
message, const { error } = await supabase.from("notifications").insert({
type, user_id: userId,
}); title,
message,
type,
});
if (error) throw error; if (error) throw error;
}, },
@ -287,34 +324,37 @@ export const notificationService = {
// Real-time subscriptions // Real-time subscriptions
export const realtimeService = { export const realtimeService = {
subscribeToUserNotifications(userId: string, callback: (notification: any) => void) { subscribeToUserNotifications(
userId: string,
callback: (notification: any) => void,
) {
return supabase return supabase
.channel(`notifications:${userId}`) .channel(`notifications:${userId}`)
.on( .on(
'postgres_changes', "postgres_changes",
{ {
event: 'INSERT', event: "INSERT",
schema: 'public', schema: "public",
table: 'notifications', table: "notifications",
filter: `user_id=eq.${userId}`, filter: `user_id=eq.${userId}`,
}, },
callback callback,
) )
.subscribe(); .subscribe();
}, },
subscribeToCommunityPosts(callback: (post: any) => void) { subscribeToCommunityPosts(callback: (post: any) => void) {
return supabase return supabase
.channel('community_posts') .channel("community_posts")
.on( .on(
'postgres_changes', "postgres_changes",
{ {
event: 'INSERT', event: "INSERT",
schema: 'public', schema: "public",
table: 'community_posts', table: "community_posts",
filter: 'is_published=eq.true', filter: "is_published=eq.true",
}, },
callback callback,
) )
.subscribe(); .subscribe();
}, },

View file

@ -1,17 +1,20 @@
import { createClient } from '@supabase/supabase-js'; import { createClient } from "@supabase/supabase-js";
import type { Database } from './database.types'; import type { Database } from "./database.types";
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
// Check if Supabase is configured // Check if Supabase is configured
export const isSupabaseConfigured = !!(supabaseUrl && supabaseAnonKey && export const isSupabaseConfigured = !!(
supabaseUrl !== 'https://your-project-ref.supabase.co' && supabaseUrl &&
supabaseAnonKey !== 'your-anon-key-here'); supabaseAnonKey &&
supabaseUrl !== "https://your-project-ref.supabase.co" &&
supabaseAnonKey !== "your-anon-key-here"
);
// Use fallback values for development if not configured // Use fallback values for development if not configured
const fallbackUrl = 'https://demo.supabase.co'; const fallbackUrl = "https://demo.supabase.co";
const fallbackKey = 'demo-key'; const fallbackKey = "demo-key";
export const supabase = createClient<Database>( export const supabase = createClient<Database>(
supabaseUrl || fallbackUrl, supabaseUrl || fallbackUrl,
@ -20,9 +23,9 @@ export const supabase = createClient<Database>(
auth: { auth: {
autoRefreshToken: isSupabaseConfigured, autoRefreshToken: isSupabaseConfigured,
persistSession: isSupabaseConfigured, persistSession: isSupabaseConfigured,
detectSessionInUrl: isSupabaseConfigured detectSessionInUrl: isSupabaseConfigured,
} },
} },
); );
// Auth helpers // Auth helpers

View file

@ -56,7 +56,8 @@ export default function Login() {
}); });
aethexToast.success({ aethexToast.success({
title: "Account created!", 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); setIsSignUp(false);
} else { } else {
@ -70,7 +71,7 @@ export default function Login() {
} }
}; };
const handleSocialLogin = async (provider: 'github' | 'google') => { const handleSocialLogin = async (provider: "github" | "google") => {
setIsLoading(true); setIsLoading(true);
try { try {
await signInWithOAuth(provider); await signInWithOAuth(provider);
@ -125,8 +126,7 @@ export default function Login() {
<CardDescription> <CardDescription>
{isSignUp {isSignUp
? "Create your AeThex account to get started" ? "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> </CardDescription>
</div> </div>
<Badge <Badge
@ -221,7 +221,9 @@ export default function Login() {
type="password" type="password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} 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" className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400"
required required
minLength={isSignUp ? 6 : undefined} minLength={isSignUp ? 6 : undefined}
@ -255,7 +257,9 @@ export default function Login() {
<Button <Button
type="submit" 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" 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" /> <LogIn className="h-4 w-4 mr-2" />
{isSignUp ? "Create Account" : "Sign In to Dashboard"} {isSignUp ? "Create Account" : "Sign In to Dashboard"}
@ -265,7 +269,9 @@ export default function Login() {
<div className="text-center pt-4"> <div className="text-center pt-4">
<p className="text-sm text-muted-foreground"> <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 <button
onClick={() => setIsSignUp(!isSignUp)} onClick={() => setIsSignUp(!isSignUp)}
className="text-aethex-400 hover:underline font-medium" className="text-aethex-400 hover:underline font-medium"