Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions database-setup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 $$
Expand All @@ -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);
2 changes: 1 addition & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
115 changes: 113 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1044,8 +1131,32 @@ export default function App({ user, onSignIn, onSignUp, onSignOut, onShowDashboa
{quiz.map((q, idx) => (
<div key={idx} className="glass-card question-card">
<div className="question-header">
<span className="question-number">Q{idx + 1}</span>
<p className="question-text">{q.question}</p>
<div className="question-header-left">
<span className="question-number">Q{idx + 1}</span>
<p className="question-text">{q.question}</p>
</div>
{user && (
<button
onClick={() => handleBookmarkToggle(idx)}
disabled={bookmarkLoading.has(idx)}
className={`bookmark-btn ${
bookmarkedQuestions.has(`${q.question}-${q.answer}`) ? 'bookmarked' : ''
}`}
title={
bookmarkedQuestions.has(`${q.question}-${q.answer}`)
? 'Remove bookmark'
: 'Bookmark this question'
}
>
{bookmarkLoading.has(idx) ? (
<span className="bookmark-loading">⟳</span>
) : bookmarkedQuestions.has(`${q.question}-${q.answer}`) ? (
<span className="bookmark-icon bookmarked">🔖</span>
) : (
<span className="bookmark-icon">📌</span>
)}
</button>
)}
</div>
<div className="options-grid">
{q.options.map((opt, i) => (
Expand Down
Loading
Loading