Prettier format pending files
This commit is contained in:
parent
ad88034b7a
commit
7097f8408b
10 changed files with 877 additions and 718 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,21 +346,21 @@ 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) {
|
||||||
|
|
@ -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) {
|
||||||
|
|
@ -373,9 +388,9 @@ export const aethexAchievementService = {
|
||||||
// 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();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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"];
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
JSON.stringify(earnedIds),
|
||||||
|
);
|
||||||
|
|
||||||
const achievement = DEMO_ACHIEVEMENTS.find(a => a.id === achievementId);
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -179,7 +200,9 @@ export const achievementService = {
|
||||||
|
|
||||||
// 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();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue