diff --git a/database-setup.sql b/database-setup.sql index b93639a..b547fc3 100644 --- a/database-setup.sql +++ b/database-setup.sql @@ -25,12 +25,31 @@ CREATE TABLE IF NOT EXISTS public.quiz_history ( created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); +-- Create bookmarked_questions table to store user's saved questions +CREATE TABLE IF NOT EXISTS public.bookmarked_questions ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL, + question TEXT NOT NULL, + options JSONB NOT NULL, -- array of answer options + correct_answer TEXT NOT NULL, + explanation TEXT, + category TEXT NOT NULL, + difficulty TEXT NOT NULL, + notes TEXT, -- user's personal notes about the question + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + -- Ensure uniqueness: same user cannot bookmark the same question twice + UNIQUE(user_id, question, correct_answer) +); + -- Enable RLS on profiles table ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; -- Enable RLS on quiz_history table ALTER TABLE public.quiz_history ENABLE ROW LEVEL SECURITY; +-- Enable RLS on bookmarked_questions table +ALTER TABLE public.bookmarked_questions ENABLE ROW LEVEL SECURITY; + -- Create policies for profiles table CREATE POLICY "Users can view own profile" ON public.profiles FOR SELECT USING (auth.uid() = id); @@ -48,6 +67,19 @@ CREATE POLICY "Users can view own quiz history" ON public.quiz_history CREATE POLICY "Users can insert own quiz history" ON public.quiz_history FOR INSERT WITH CHECK (auth.uid() = user_id); +-- Create policies for bookmarked_questions table +CREATE POLICY "Users can view own bookmarks" ON public.bookmarked_questions + FOR SELECT USING (auth.uid() = user_id); + +CREATE POLICY "Users can insert own bookmarks" ON public.bookmarked_questions + FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own bookmarks" ON public.bookmarked_questions + FOR UPDATE USING (auth.uid() = user_id); + +CREATE POLICY "Users can delete own bookmarks" ON public.bookmarked_questions + FOR DELETE USING (auth.uid() = user_id); + -- Create function to automatically create profile on user signup CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS TRIGGER AS $$ @@ -68,3 +100,8 @@ CREATE TRIGGER on_auth_user_created CREATE INDEX IF NOT EXISTS idx_quiz_history_user_id ON public.quiz_history(user_id); CREATE INDEX IF NOT EXISTS idx_quiz_history_created_at ON public.quiz_history(created_at DESC); CREATE INDEX IF NOT EXISTS idx_quiz_history_category ON public.quiz_history(category); + +-- Create indexes for bookmarked_questions +CREATE INDEX IF NOT EXISTS idx_bookmarked_questions_user_id ON public.bookmarked_questions(user_id); +CREATE INDEX IF NOT EXISTS idx_bookmarked_questions_created_at ON public.bookmarked_questions(created_at DESC); +CREATE INDEX IF NOT EXISTS idx_bookmarked_questions_category ON public.bookmarked_questions(category); diff --git a/package.json b/package.json index f200ab6..7a5dcc7 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { + "dev": "vite", "build": "vite build", "lint": "eslint .", "preview": "vite preview", diff --git a/server.js b/server.js index 66f330b..0c5c223 100644 --- a/server.js +++ b/server.js @@ -11,7 +11,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); -const PORT = process.env.PORT || 5174; +const PORT = process.env.PORT || 3001; // Changed from 5174 to 3001 app.use(cors()); app.use(express.json()); diff --git a/src/App.jsx b/src/App.jsx index 225ccee..2a75f1f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,6 +8,7 @@ import NotificationBadge from "./components/NotificationBadge"; import GlassmorphicDropdown from "./components/GlassmorphicDropdown"; import { AuthProvider, useAuth } from "./contexts/AuthContext"; import { quizService } from "./services/quizService"; +import { bookmarkService } from "./services/bookmarkService"; import { jsPDF } from 'jspdf'; // Import jsPDF import './components/Result.css' @@ -374,6 +375,10 @@ export default function App({ user, onSignIn, onSignUp, onSignOut, onShowDashboa const [showStartScreen, setShowStartScreen] = useState(true); const [originalQuiz, setOriginalQuiz] = useState([]); const [showExamPrepPage, setShowExamPrepPage] = useState(false); + + // Bookmark states + const [bookmarkedQuestions, setBookmarkedQuestions] = useState(new Set()); + const [bookmarkLoading, setBookmarkLoading] = useState(new Set()); const [isDarkMode, setIsDarkMode] = useState(() => { @@ -559,6 +564,88 @@ export default function App({ user, onSignIn, onSignUp, onSignOut, onShowDashboa setShowStartScreen(true); } + // Bookmark functions + const checkBookmarkStatus = async (question) => { + if (!user) return; + + try { + const result = await bookmarkService.isBookmarked(question.question, question.answer); + if (result.isBookmarked) { + setBookmarkedQuestions(prev => new Set([...prev, `${question.question}-${question.answer}`])); + } + } catch (error) { + console.error('Error checking bookmark status:', error); + } + }; + + const handleBookmarkToggle = async (questionIndex) => { + if (!user) { + // Show sign-in prompt or modal + alert('Please sign in to bookmark questions'); + return; + } + + const question = quiz[questionIndex]; + const questionKey = `${question.question}-${question.answer}`; + + setBookmarkLoading(prev => new Set([...prev, questionIndex])); + + try { + const isCurrentlyBookmarked = bookmarkedQuestions.has(questionKey); + + if (isCurrentlyBookmarked) { + // Remove bookmark + const result = await bookmarkService.removeBookmarkByQuestion(question.question, question.answer); + if (result.success) { + setBookmarkedQuestions(prev => { + const newSet = new Set(prev); + newSet.delete(questionKey); + return newSet; + }); + } else { + console.error('Error removing bookmark:', result.error); + } + } else { + // Add bookmark + const bookmarkData = { + question: question.question, + options: question.options, + answer: question.answer, + explanation: question.explanation, + category: selectedCategory, + difficulty: selectedDifficulty + }; + + const result = await bookmarkService.saveBookmark(bookmarkData); + if (result.data) { + setBookmarkedQuestions(prev => new Set([...prev, questionKey])); + } else if (result.error === 'Question already bookmarked') { + // Question was already bookmarked, update UI state + setBookmarkedQuestions(prev => new Set([...prev, questionKey])); + } else { + console.error('Error saving bookmark:', result.error); + } + } + } catch (error) { + console.error('Error toggling bookmark:', error); + } finally { + setBookmarkLoading(prev => { + const newSet = new Set(prev); + newSet.delete(questionIndex); + return newSet; + }); + } + }; + + // Check bookmark status when quiz loads + useEffect(() => { + if (quiz.length > 0 && user) { + quiz.forEach(question => { + checkBookmarkStatus(question); + }); + } + }, [quiz, user]); + // PDF Generation Function const generatePDF = () => { try { @@ -1044,8 +1131,32 @@ export default function App({ user, onSignIn, onSignUp, onSignOut, onShowDashboa {quiz.map((q, idx) => (
{q.question}
+{q.question}
+Loading your bookmarked questions...
+{error}
+ ++ {searchTerm || selectedCategory !== 'All Categories' + ? 'Try adjusting your filters or search terms.' + : 'Start bookmarking interesting questions during quizzes to see them here!' + } +
+{bookmark.explanation}
++ {bookmark.notes || 'No notes added yet.'} +
+ )} +