initial commit: rocket.new export of broadcastbeat
This commit is contained in:
273
src/contexts/AuthContext.tsx
Normal file
273
src/contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,273 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { createClient } from '../lib/supabase/client';
|
||||
|
||||
const AuthContext = createContext<any>({});
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [user, setUser] = useState<any>(null);
|
||||
const [session, setSession] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const supabase = createClient();
|
||||
|
||||
useEffect(() => {
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
const {
|
||||
data: { subscription }
|
||||
} = supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
// Email/Password Sign Up
|
||||
const signUp = async (email: string, password: string, metadata = {}) => {
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: {
|
||||
full_name: (metadata as any)?.fullName || '',
|
||||
avatar_url: (metadata as any)?.avatarUrl || ''
|
||||
},
|
||||
emailRedirectTo: `${window.location.origin}/auth/callback`
|
||||
}
|
||||
});
|
||||
if (error) throw error;
|
||||
return data;
|
||||
};
|
||||
|
||||
// Email/Password Sign In
|
||||
const signIn = async (email: string, password: string) => {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password
|
||||
});
|
||||
if (error) throw error;
|
||||
return data;
|
||||
};
|
||||
|
||||
// Sign Out
|
||||
const signOut = async () => {
|
||||
const { error } = await supabase.auth.signOut();
|
||||
if (error) throw error;
|
||||
};
|
||||
|
||||
// Get Current User
|
||||
const getCurrentUser = async () => {
|
||||
const { data: { user }, error } = await supabase.auth.getUser();
|
||||
if (error) throw error;
|
||||
return user;
|
||||
};
|
||||
|
||||
// Check if Email is Verified
|
||||
const isEmailVerified = () => {
|
||||
return user?.email_confirmed_at !== null;
|
||||
};
|
||||
|
||||
// Get User Profile from Database
|
||||
const getUserProfile = async () => {
|
||||
if (!user) return null;
|
||||
const { data, error } = await supabase
|
||||
.from('user_profiles')
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
if (error) throw error;
|
||||
return data;
|
||||
};
|
||||
|
||||
// ── Bookmarks (reading_list) ──────────────────────────────
|
||||
|
||||
const getBookmarks = async () => {
|
||||
if (!user) return [];
|
||||
const { data, error } = await supabase
|
||||
.from('reading_list')
|
||||
.select('*')
|
||||
.eq('user_id', user.id)
|
||||
.order('saved_at', { ascending: false });
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
};
|
||||
|
||||
const addBookmark = async (article: {
|
||||
slug: string;
|
||||
title: string;
|
||||
excerpt?: string;
|
||||
image?: string;
|
||||
alt?: string;
|
||||
category?: string;
|
||||
author?: string;
|
||||
readTime?: string;
|
||||
date?: string;
|
||||
}) => {
|
||||
if (!user) throw new Error('Not authenticated');
|
||||
const { error } = await supabase.from('reading_list').insert({
|
||||
user_id: user.id,
|
||||
article_slug: article.slug,
|
||||
article_title: article.title,
|
||||
article_excerpt: article.excerpt ?? null,
|
||||
article_image: article.image ?? null,
|
||||
article_image_alt: article.alt ?? null,
|
||||
article_category: article.category ?? null,
|
||||
article_author: article.author ?? null,
|
||||
article_read_time: article.readTime ?? null,
|
||||
article_date: article.date ?? null,
|
||||
});
|
||||
if (error) throw error;
|
||||
};
|
||||
|
||||
const removeBookmark = async (articleSlug: string) => {
|
||||
if (!user) throw new Error('Not authenticated');
|
||||
const { error } = await supabase
|
||||
.from('reading_list')
|
||||
.delete()
|
||||
.eq('user_id', user.id)
|
||||
.eq('article_slug', articleSlug);
|
||||
if (error) throw error;
|
||||
};
|
||||
|
||||
const isBookmarked = async (articleSlug: string): Promise<boolean> => {
|
||||
if (!user) return false;
|
||||
const { data } = await supabase
|
||||
.from('reading_list')
|
||||
.select('id')
|
||||
.eq('user_id', user.id)
|
||||
.eq('article_slug', articleSlug)
|
||||
.maybeSingle();
|
||||
return !!data;
|
||||
};
|
||||
|
||||
// ── Reading History ───────────────────────────────────────
|
||||
|
||||
const getReadingHistory = async () => {
|
||||
if (!user) return [];
|
||||
const { data, error } = await supabase
|
||||
.from('reading_history')
|
||||
.select('*')
|
||||
.eq('user_id', user.id)
|
||||
.order('viewed_at', { ascending: false });
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
};
|
||||
|
||||
const trackArticleView = async (article: {
|
||||
slug: string;
|
||||
title: string;
|
||||
excerpt?: string;
|
||||
image?: string;
|
||||
alt?: string;
|
||||
category?: string;
|
||||
author?: string;
|
||||
readTime?: string;
|
||||
date?: string;
|
||||
}) => {
|
||||
if (!user) return;
|
||||
// Upsert so each article appears once (most recent view)
|
||||
const { error } = await supabase.from('reading_history').upsert(
|
||||
{
|
||||
user_id: user.id,
|
||||
article_slug: article.slug,
|
||||
article_title: article.title,
|
||||
article_excerpt: article.excerpt ?? null,
|
||||
article_image: article.image ?? null,
|
||||
article_image_alt: article.alt ?? null,
|
||||
article_category: article.category ?? null,
|
||||
article_author: article.author ?? null,
|
||||
article_read_time: article.readTime ?? null,
|
||||
article_date: article.date ?? null,
|
||||
viewed_at: new Date().toISOString(),
|
||||
},
|
||||
{ onConflict: 'user_id,article_slug' }
|
||||
);
|
||||
if (error) {
|
||||
// Silent fail — history tracking should not break the page
|
||||
}
|
||||
};
|
||||
|
||||
const clearReadingHistory = async () => {
|
||||
if (!user) throw new Error('Not authenticated');
|
||||
const { error } = await supabase
|
||||
.from('reading_history')
|
||||
.delete()
|
||||
.eq('user_id', user.id);
|
||||
if (error) throw error;
|
||||
};
|
||||
|
||||
// ── Topic Preferences ─────────────────────────────────────
|
||||
|
||||
const getTopicPreferences = async () => {
|
||||
if (!user) return [];
|
||||
const { data, error } = await supabase
|
||||
.from('user_topic_preferences')
|
||||
.select('*')
|
||||
.eq('user_id', user.id)
|
||||
.order('view_count', { ascending: false });
|
||||
if (error) throw error;
|
||||
return data || [];
|
||||
};
|
||||
|
||||
const trackTopicView = async (topic: string) => {
|
||||
if (!user || !topic) return;
|
||||
// Upsert: increment view_count if topic already exists
|
||||
const { error } = await supabase.rpc
|
||||
? await supabase
|
||||
.from('user_topic_preferences')
|
||||
.upsert(
|
||||
{
|
||||
user_id: user.id,
|
||||
topic: topic.toLowerCase().trim(),
|
||||
view_count: 1,
|
||||
last_viewed_at: new Date().toISOString(),
|
||||
},
|
||||
{ onConflict: 'user_id,topic' }
|
||||
)
|
||||
: { error: null };
|
||||
if (error) {
|
||||
// Silent fail
|
||||
}
|
||||
};
|
||||
|
||||
const value = {
|
||||
user,
|
||||
session,
|
||||
loading,
|
||||
signUp,
|
||||
signIn,
|
||||
signOut,
|
||||
getCurrentUser,
|
||||
isEmailVerified,
|
||||
getUserProfile,
|
||||
// Bookmarks
|
||||
getBookmarks,
|
||||
addBookmark,
|
||||
removeBookmark,
|
||||
isBookmarked,
|
||||
// Reading History
|
||||
getReadingHistory,
|
||||
trackArticleView,
|
||||
clearReadingHistory,
|
||||
// Topic Preferences
|
||||
getTopicPreferences,
|
||||
trackTopicView,
|
||||
};
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
};
|
||||
Reference in New Issue
Block a user