0% found this document useful (0 votes)
215 views26 pages

Import React

The document outlines the implementation of a React component named StudyBuddy, which manages user authentication, search limits, and content fetching related to study topics. It includes state management for user data, request limits based on user status, and modals for login and explanations. The component also handles user interactions, such as text selection and submission of queries, while maintaining local storage for user data.

Uploaded by

Shruti Rapolu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
215 views26 pages

Import React

The document outlines the implementation of a React component named StudyBuddy, which manages user authentication, search limits, and content fetching related to study topics. It includes state management for user data, request limits based on user status, and modals for login and explanations. The component also handles user interactions, such as text selection and submission of queries, while maintaining local storage for user data.

Uploaded by

Shruti Rapolu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 26

import React, { useState, useEffect, useRef } from "react";

import { useNavigate } from "react-router-dom";

import ReactMarkdown from "react-markdown";

const StudyBuddy = () => {

const [topic, setTopic] = useState("");

const [selectedType, setSelectedType] = useState("both");

const [notes, setNotes] = useState("");

const [videos, setVideos] = useState([]);

const [loading, setLoading] = useState(false);

const [error, setError] = useState("");

const [searchedTopic, setSearchedTopic] = useState("");

const [showLoginModal, setShowLoginModal] = useState(false);

const navigate = useNavigate();

// User state for authentication and usage tracking

const [user, setUser] = useState(null);

const [userRequestsCount, setUserRequestsCount] = useState(0); // To display current daily usage

// State for selected text pop-up and explanation modal

const [selectedText, setSelectedText] = useState("");

const [showPopup, setShowPopup] = useState(false);

const [popupPosition, setPopupPosition] = useState({ x: 0, y: 0 });

const notesRef = useRef(null);

const popupRef = useRef(null);

const [buddyExplanation, setBuddyExplanation] = useState("");

const [showExplanationModal, setShowExplanationModal] = useState(false);

const [explanationLoading, setExplanationLoading] = useState(false);

// Dummy Payment Modal State

const [showDummyPaymentModal, setShowDummyPaymentModal] = useState(false);

// Define limits more clearly

const ANONYMOUS_FREE_LIMIT = 1; // 1 free search for anonymous users

const LOGGED_IN_FREE_LIMIT = 2; // 2 free searches for logged-in non-premium users

// Determine the effective DAILY_FREE_LIMIT based on user status

const effectiveDailyLimit = user?.isPremium


? Infinity // Premium users have no limit

: user

? LOGGED_IN_FREE_LIMIT

: ANONYMOUS_FREE_LIMIT;

// Determine if the user has reached their limit

const hasReachedLimit = userRequestsCount >= effectiveDailyLimit;

// --- START OF CRITICAL CHANGES ---

// Refactored `updateUsageAndUser` for consistency

const updateUsageAndUser = (updatedUser) => {

// Ensure lastRequestDate is always updated to the current time for the latest activity

updatedUser.lastRequestDate = new Date().toISOString();

// Set component state

setUser(updatedUser);

setUserRequestsCount(updatedUser.dailyRequestsCount || 0);

// Persist to localStorage

if (updatedUser.isAnonymous) {

localStorage.setItem(

`anonUser_${updatedUser._id}`,

JSON.stringify(updatedUser)

);

localStorage.setItem("anonymousUserId", updatedUser._id); // Keep general pointer

} else {

// For logged-in users, always update the "user" key

const userToStore = { ...updatedUser };

delete userToStore.password; // Never store password in localStorage

localStorage.setItem("user", JSON.stringify(userToStore));

};

useEffect(() => {

// Attempt to load logged-in user first

const storedUser = localStorage.getItem("user");

if (storedUser) {

const parsedUser = JSON.parse(storedUser);


const lastRequestDate = new Date(parsedUser.lastRequestDate);

const today = new Date();

// Reset count for logged-in non-premium users if it's a new day

if (

!parsedUser.isPremium &&

lastRequestDate.toDateString() !== today.toDateString()

){

parsedUser.dailyRequestsCount = 0;

parsedUser.lastRequestDate = today.toISOString();

// Update localStorage immediately with the reset count

localStorage.setItem("user", JSON.stringify(parsedUser));

setUser(parsedUser);

setUserRequestsCount(parsedUser.dailyRequestsCount || 0);

} else {

// If no logged-in user, try to load anonymous user

const anonymousUserId = localStorage.getItem("anonymousUserId");

if (anonymousUserId) {

const storedAnonymousData = localStorage.getItem(

`anonUser_${anonymousUserId}`

);

if (storedAnonymousData) {

const parsedAnonymousData = JSON.parse(storedAnonymousData);

const lastAnonymousRequestDate = new Date(

parsedAnonymousData.lastRequestDate

);

const today = new Date();

// Reset count if it's a new day for anonymous user

if (

lastAnonymousRequestDate.toDateString() !== today.toDateString()

){

parsedAnonymousData.dailyRequestsCount = 0;

parsedAnonymousData.lastRequestDate = today.toISOString();

localStorage.setItem(

`anonUser_${anonymousUserId}`,

JSON.stringify(parsedAnonymousData)

);
}

// Set user state for anonymous user from the (potentially updated) stored data

setUser({

_id: anonymousUserId,

isAnonymous: true,

isPremium: false,

dailyRequestsCount: parsedAnonymousData.dailyRequestsCount || 0,

lastRequestDate: parsedAnonymousData.lastRequestDate,

});

setUserRequestsCount(parsedAnonymousData.dailyRequestsCount || 0);

} else {

// anonymousUserId found, but no detailed data. This can happen if anonUserId was set but data wasn't.

// In this case, treat as a fresh anonymous session for quota tracking, but keep the ID.

// The next request will correctly populate `anonUser_${anonymousUserId}`

setUser({

_id: anonymousUserId,

isAnonymous: true,

isPremium: false,

dailyRequestsCount: 0,

lastRequestDate: new Date().toISOString(),

});

setUserRequestsCount(0);

} else {

// No user (neither logged-in nor anonymous ID found)

setUser(null);

setUserRequestsCount(0);

document.addEventListener("mousedown", handleClickOutside);

return () => {

document.removeEventListener("mousedown", handleClickOutside);

};

}, []); // Empty dependency array means this runs once on mount

// --- END OF CRITICAL CHANGES ---

const handleClickOutside = (event) => {


if (popupRef.current && !popupRef.current.contains(event.target)) {

setShowPopup(false);

setSelectedText("");

window.getSelection().removeAllRanges();

};

const handleTextSelection = (e) => {

setTimeout(() => {

const selection = window.getSelection();

const text = selection.toString().trim();

if (

text.length > 0 &&

notesRef.current &&

notesRef.current.contains(selection.anchorNode)

){

setSelectedText(text);

setShowPopup(true);

const range = selection.getRangeAt(0);

const rect = range.getBoundingClientRect();

setPopupPosition({

x: rect.left + window.scrollX + rect.width / 2,

y: rect.top + window.scrollY - 40,

});

} else {

setShowPopup(false);

setSelectedText("");

}, 100);

};

const commonFetchOptions = (method, body) => {

const headers = { "Content-Type": "application/json" };

if (user && user._id) {

headers["X-User-Id"] = user._id; // Send user ID to backend for tracking

return {
method,

headers,

body: JSON.stringify(body),

};

};

// Centralized function to check limits and show modal

const checkAndHandleLimit = () => {

if (user && user.isPremium) {

return false; // Premium users have no limit

// If user is anonymous and hits their limit

if (!user && userRequestsCount >= ANONYMOUS_FREE_LIMIT) {

// This case means no user object is set yet, but current anonymous count exceeds limit

setShowLoginModal(true);

setError("You've used your free search. Please log in to continue!");

return true;

// If an anonymous user object exists and they've hit their limit

if (user && user.isAnonymous && userRequestsCount >= ANONYMOUS_FREE_LIMIT) {

setShowLoginModal(true);

setError(

"You've used your free anonymous search. Please log in to continue!"

);

return true;

// If logged in (non-premium) and hits their limit

if (user && !user.isPremium && userRequestsCount >= LOGGED_IN_FREE_LIMIT) {

setShowDummyPaymentModal(true);

setError(

`You've used your ${LOGGED_IN_FREE_LIMIT} free requests for today. Please upgrade to continue!`

);

return true;

return false; // Limit not reached

};
const handleSubmit = async (e) => {

if (e) e.preventDefault();

if (checkAndHandleLimit()) {

return; // Stop if limit reached

setLoading(true);

setError("");

setNotes("");

setVideos([]);

setSearchedTopic(topic);

setShowPopup(false);

setShowExplanationModal(false);

try {

const response = await fetch(

"http://localhost:5000/api/study",

commonFetchOptions("POST", { topic, type: selectedType })

);

const newUserId = response.headers.get("X-User-Id");

// Determine the user object to update

let userToUpdate = { ...user }; // Start with current user state or an empty object

if (!user) {

// First request for a completely new anonymous user

userToUpdate = {

_id: newUserId || `anon_${Date.now()}`,

isAnonymous: true,

isPremium: false,

dailyRequestsCount: 1,

lastRequestDate: new Date().toISOString(),

};

} else if (!user.isPremium) {

// Increment count for existing anonymous or logged-in non-premium user

userToUpdate.dailyRequestsCount =

(userToUpdate.dailyRequestsCount || 0) + 1;
}

// Always update usage and user state after a successful request

updateUsageAndUser(userToUpdate);

if (!response.ok) {

const errorData = await response.json();

// Backend also sends LIMIT_EXCEEDED, useful if client-side check is bypassed

if (errorData.code === "LIMIT_EXCEEDED") {

setError(errorData.error);

if (user && user.isAnonymous) {

setShowLoginModal(true); // After anonymous limit, prompt login

} else {

setShowDummyPaymentModal(true); // For logged-in non-premium, prompt upgrade

return;

throw new Error(errorData.error || `Status: ${response.status}`);

const data = await response.json();

if (data.notes) setNotes(data.notes);

if (data.videos) setVideos(data.videos);

} catch (err) {

setError(`Failed to fetch: ${err.message}`);

} finally {

setLoading(false);

};

const handleAskBuddy = async () => {

if (!selectedText) return;

if (checkAndHandleLimit()) {

return; // Stop if limit reached

setExplanationLoading(true);

setBuddyExplanation("");

setShowExplanationModal(true);
setShowPopup(false);

try {

const response = await fetch(

"http://localhost:5000/api/explain",

commonFetchOptions("POST", { selectedText, contextNotes: notes })

);

// Only increment if not premium

if (user && !user.isPremium) {

const updatedUser = {

...user,

dailyRequestsCount: (user.dailyRequestsCount || 0) + 1,

};

updateUsageAndUser(updatedUser);

if (!response.ok) {

const errorData = await response.json();

if (errorData.code === "LIMIT_EXCEEDED") {

setError(errorData.error);

setShowExplanationModal(false);

if (user && user.isAnonymous) {

setShowLoginModal(true); // After anonymous limit, prompt login

} else {

setShowDummyPaymentModal(true); // For logged-in non-premium, prompt upgrade

return;

throw new Error(errorData.error || `Status: ${response.status}`);

const data = await response.json();

setBuddyExplanation(data.explanation);

} catch (err) {

setBuddyExplanation(`Failed to get explanation: ${err.message}`);

} finally {

setExplanationLoading(false);

setSelectedText("");
window.getSelection().removeAllRanges();

};

const handleDemoLogin = () => {

// Clear any previous anonymous data from localStorage

const currentAnonymousId = localStorage.getItem("anonymousUserId");

if (currentAnonymousId) {

localStorage.removeItem(`anonUser_${currentAnonymousId}`);

localStorage.removeItem("anonymousUserId");

const mockLoggedInUser = {

_id: "loggedInUser123",

username: "StudyMaster",

email: "[email protected]",

isPremium: false, // Set to false to test limits for logged-in users too

dailyRequestsCount: 0, // Reset count on login for a fresh start for logged-in non-premium

isAnonymous: false,

lastRequestDate: new Date().toISOString(),

};

setUser(mockLoggedInUser);

setUserRequestsCount(0); // Reset for the new logged-in state

localStorage.setItem("user", JSON.stringify(mockLoggedInUser));

setShowLoginModal(false);

};

const mockLogout = () => {

// Clear logged-in user data

localStorage.removeItem("user");

// Also clear the last known anonymous ID if it exists, to prevent conflict

localStorage.removeItem("anonymousUserId");

// Note: Individual anonUser_ID data might remain, but a new anonymous session will get a new ID.

// If you want to purge all anonymous session data on logout, you'd need a more complex cleanup.

setUser(null);

setUserRequestsCount(0);

};

const LoginModal = ({ onLogin, onClose }) => (


<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">

<div className="bg-white rounded-3xl shadow-2xl p-8 max-w-md w-full transform animate-scale-in">

<div className="text-center mb-6">

<div className="w-16 h-16 bg-gradient-to-br from-sky-400 to-blue-500 rounded-2xl flex items-center


justify-center mx-auto mb-4 shadow-xl">

<span className="text-3xl">🔐</span>

</div>

<h2 className="text-2xl font-bold text-sky-700 mb-2">

Login Required

</h2>

<p className="text-sky-600">

You've used your free submission! Please login to continue learning.

</p>

</div>

<div className="space-y-4">

<button

onClick={onLogin}

className="w-full bg-gradient-to-r from-sky-500 to-blue-500 hover:from-sky-600 hover:to-blue-600


text-white font-bold py-3 px-6 rounded-xl transition-all duration-300 transform hover:scale-105 shadow-lg"

>

Login with Demo Account

</button>

<button

onClick={onClose}

className="w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-semibold py-3 px-6 rounded-xl


transition-all duration-300"

>

Maybe Later

</button>

</div>

</div>

</div>

);

const ExplanationModal = ({

explanation,

loading,

onClose,

selectedText,
}) => (

<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">

<div className="bg-white rounded-3xl shadow-2xl p-8 max-w-2xl w-full transform animate-scale-in">

<div className="flex justify-between items-center mb-6">

<h2 className="text-3xl font-bold text-sky-700 flex items-center space-x-3">

<span className="text-4xl">🧠</span>

<span>Buddy's Explanation</span>

</h2>

<button

onClick={onClose}

className="text-gray-500 hover:text-gray-700 transition-colors text-3xl font-bold"

>

&times;

</button>

</div>

{loading ? (

<div className="flex flex-col items-center justify-center py-10">

<div className="animate-spin rounded-full h-12 w-12 border-4 border-sky-400 border-t-transparent


mb-4"></div>

<p className="text-sky-600 text-lg">Buddy is thinking...</p>

</div>

):(

<>

{selectedText && (

<div className="bg-sky-50 border-l-4 border-sky-200 p-4 mb-6 rounded-lg italic text-sky-700">

<p className="font-semibold mb-1">You asked about:</p>

<p className="text-base">"{selectedText}"</p>

</div>

)}

<div className="bg-sky-50 rounded-2xl p-6 border border-sky-200 shadow-inner max-h-96 overflow-y-


auto prose prose-lg prose-sky max-w-none text-sky-800 leading-relaxed">

{explanation ? (

<ReactMarkdown>{explanation}</ReactMarkdown>

):(

<p className="text-gray-500">No explanation available.</p>

)}

</div>

</>

)}
<div className="text-right mt-6">

<button

onClick={onClose}

className="bg-sky-100 hover:bg-sky-200 text-sky-700 font-semibold py-2 px-5 rounded-xl transition-all


duration-300"

>

Close

</button>

</div>

</div>

</div>

);

// Dummy Payment Modal Component

const DummyPaymentModal = ({ onClose, onSimulateTomorrow }) => {

const handleSimulateTomorrow = () => {

// For demo purposes, simulate backend resetting the count

// In a real scenario, this would be handled by backend logic (e.g., after 24 hours)

if (user) {

onSimulateTomorrow({

...user,

dailyRequestsCount: 0,

lastRequestDate: new Date().toISOString(),

});

onClose(); // Close the modal

};

return (

<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">

<div className="bg-white rounded-3xl shadow-2xl p-8 max-w-md w-full transform animate-scale-in">

<div className="text-center mb-6">

<div className="w-16 h-16 bg-gradient-to-br from-red-400 to-orange-500 rounded-2xl flex items-


center justify-center mx-auto mb-4 shadow-xl">

<span className="text-3xl">🚫</span>

</div>

<h2 className="text-2xl font-bold text-orange-700 mb-2">

Daily Limit Reached - Demo Mode

</h2>
<p className="text-orange-600">

You've used your {effectiveDailyLimit} free requests for today.

This is a demo payment page.

<br />

<br />

**Please come back tomorrow for more free uses!**

</p>

<p className="text-lg font-bold text-orange-800 mt-4">

(In a real app, this is where a payment gateway like Razorpay

would appear.)

</p>

</div>

<div className="space-y-4">

<button

onClick={handleSimulateTomorrow}

className="w-full bg-gradient-to-r from-green-500 to-teal-500 hover:from-green-600 hover:to-teal-


600 text-white font-bold py-3 px-6 rounded-xl transition-all duration-300 transform hover:scale-105 shadow-lg"

>

Simulate Tomorrow / Reset Daily Limit

</button>

<button

onClick={onClose}

className="w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-semibold py-3 px-6 rounded-xl


transition-all duration-300"

>

Close

</button>

</div>

</div>

</div>

);

};

const handleSimulateTomorrow = (updatedUser) => {

// This is called from DummyPaymentModal to simulate the next day's access

updateUsageAndUser(updatedUser);

};

return (
<div className="min-h-screen bg-gradient-to-br from-sky-50 via-blue-50 to-cyan-50">

<div className="container mx-auto max-w-5xl px-4 py-8">

{/* Header with Auth Status */}

<div className="flex justify-between items-center mb-8">

<div></div>

<div className="flex items-center space-x-4">

{user ? (

<div className="flex items-center space-x-3 bg-white/60 backdrop-blur-sm rounded-xl px-4 py-2


border border-sky-200">

<div className="w-10 h-10 bg-gradient-to-br from-emerald-400 to-teal-500 rounded-full flex items-


center justify-center shadow-lg">

<span className="text-white font-bold">

{user.username

? user.username[0]

: user.isAnonymous

? "A"

: "?"}

</span>

</div>

<span className="text-sky-700 font-medium">

{user.isAnonymous

? "Anonymous User"

: `Welcome, ${user.username}!`}

</span>

{user.isPremium ? (

<span className="text-purple-600 font-bold text-sm">

PREMIUM ✨

</span>

):(

<span className="text-amber-600 text-sm">

{userRequestsCount} / {effectiveDailyLimit} requests used

</span>

)}

<button

onClick={mockLogout}

className="text-sky-600 hover:text-sky-800 font-medium text-sm transition-colors"

>

Logout

</button>

</div>
):(

<div className="flex items-center space-x-2 bg-white/60 backdrop-blur-sm rounded-xl px-4 py-2


border border-sky-200">

<span className="text-sky-600 text-sm">

{userRequestsCount >= ANONYMOUS_FREE_LIMIT

? "Free trial used"

: `${

ANONYMOUS_FREE_LIMIT - userRequestsCount

} free search remaining`}

</span>

<div

className={`w-2 h-2 rounded-full ${

userRequestsCount >= ANONYMOUS_FREE_LIMIT

? "bg-red-400"

: "bg-emerald-400"

}`}

></div>

</div>

)}

</div>

</div>

{/* Floating Header */}

<div className="text-center mb-12">

<div className="inline-flex items-center justify-center w-24 h-24 bg-gradient-to-br from-sky-400 to-


blue-500 rounded-3xl mb-6 shadow-2xl shadow-sky-200">

<span className="text-4xl">☁️</span>

</div>

<h1 className="text-7xl font-black text-transparent bg-clip-text bg-gradient-to-r from-sky-600 via-blue-


600 to-cyan-600 mb-4 tracking-tight">

StudyBuddy

</h1>

<p className="text-xl text-sky-600 font-medium">

Your intelligent learning companion in the cloud

</p>

</div>

{/* Main Study Card */}

<div className="bg-white/80 backdrop-blur-xl rounded-3xl shadow-2xl shadow-sky-200/50 border border-


sky-100 p-10 mb-10 hover:shadow-3xl hover:shadow-sky-300/30 transition-all duration-500">

<div className="space-y-10">
{/* Topic Input Section */}

<div className="space-y-4">

<div className="flex items-center space-x-3 mb-4">

<div className="w-10 h-10 bg-gradient-to-br from-sky-400 to-blue-500 rounded-xl flex items-center


justify-center shadow-lg">

<span className="text-white text-xl">✨</span>

</div>

<label

htmlFor="topic"

className="text-2xl font-bold text-sky-700"

>

What would you like to explore today?

</label>

</div>

<div className="relative">

<input

type="text"

id="topic"

className="w-full p-6 rounded-2xl bg-gradient-to-r from-sky-50 to-blue-50 border-2 border-sky-200


shadow-inner focus:outline-none focus:ring-4 focus:ring-sky-300/50 focus:border-sky-400 text-sky-800 text-xl
placeholder-sky-400 transition-all duration-300 hover:shadow-lg"

placeholder="e.g., Artificial Intelligence, Quantum Physics, Web Development..."

value={topic}

onChange={(e) => setTopic(e.target.value)}

required

/>

<div className="absolute inset-y-0 right-0 flex items-center pr-6">

<div className="w-8 h-8 bg-gradient-to-br from-sky-400 to-blue-500 rounded-full flex items-center


justify-center">

<span className="text-white text-lg">🔍</span>

</div>

</div>

</div>

</div>

{/* Learning Style Selection */}

<div className="space-y-6">

<div className="flex items-center space-x-3">

<div className="w-10 h-10 bg-gradient-to-br from-cyan-400 to-sky-500 rounded-xl flex items-center


justify-center shadow-lg">

<span className="text-white text-xl">🎯</span>


</div>

<h3 className="text-2xl font-bold text-sky-700">

Choose your learning style

</h3>

</div>

<div className="grid grid-cols-1 md:grid-cols-3 gap-6">

{[

type: "notes",

icon: "📚",

title: "Study Notes",

desc: "Comprehensive written content",

gradient: "from-emerald-400 to-teal-500",

},

type: "videos",

icon: "🎬",

title: "Video Library",

desc: "Visual learning resources",

gradient: "from-rose-400 to-pink-500",

},

type: "both",

icon: "🚀",

title: "Complete Suite",

desc: "Notes + Videos combined",

gradient: "from-violet-400 to-purple-500",

},

].map(({ type, icon, title, desc, gradient }) => (

<label

key={type}

className={`group relative cursor-pointer transition-all duration-300 transform hover:scale-105 $


{

selectedType === type ? "scale-105" : ""

}`}

>

<input

type="radio"

name="materialType"
value={type}

checked={selectedType === type}

onChange={() => setSelectedType(type)}

className="sr-only"

/>

<div

className={`relative p-8 rounded-2xl border-3 transition-all duration-300 ${

selectedType === type

? "bg-gradient-to-br from-sky-100 to-blue-100 border-sky-400 shadow-xl shadow-sky-200/50"

: "bg-white/60 border-sky-200 hover:border-sky-300 hover:bg-sky-50/80 shadow-lg


hover:shadow-xl"

}`}

>

<div className="text-center space-y-4">

<div

className={`inline-flex items-center justify-center w-16 h-16 bg-gradient-to-br ${gradient}


rounded-2xl shadow-lg`}

>

<span className="text-3xl">{icon}</span>

</div>

<div>

<h4 className="text-sky-800 font-bold text-xl mb-2">

{title}

</h4>

<p className="text-sky-600 text-sm leading-relaxed">

{desc}

</p>

</div>

</div>

{selectedType === type && (

<div className="absolute -top-2 -right-2 w-8 h-8 bg-gradient-to-br from-sky-500 to-blue-600


rounded-full flex items-center justify-center shadow-lg animate-bounce">

<span className="text-white text-sm font-bold">

</span>

</div>

)}

</div>

</label>

))}
</div>

</div>

{/* Generate Button */}

<div className="pt-4">

<button

onClick={handleSubmit}

// Disable button if loading, or if not premium and limit is reached

disabled={loading || (!user?.isPremium && hasReachedLimit)}

className={`w-full font-bold py-6 px-8 rounded-2xl text-xl shadow-2xl transition-all duration-300


transform hover:scale-[1.02] disabled:opacity-60 disabled:cursor-not-allowed disabled:transform-none ${

user && user.isPremium

? "bg-gradient-to-r from-sky-500 via-blue-500 to-cyan-500 hover:from-sky-600 hover:via-blue-600


hover:to-cyan-600 text-white shadow-sky-300/50 hover:shadow-sky-400/60"

: hasReachedLimit

? "bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-600 hover:to-red-600 text-white


shadow-orange-300/50 hover:shadow-orange-400/60"

: "bg-gradient-to-r from-sky-500 via-blue-500 to-cyan-500 hover:from-sky-600 hover:via-blue-600


hover:to-cyan-600 text-white shadow-sky-300/50 hover:shadow-sky-400/60"

}`}

>

{loading ? (

<div className="flex items-center justify-center space-x-4">

<div className="animate-spin rounded-full h-8 w-8 border-3 border-white border-t-


transparent"></div>

<span className="text-lg">

Crafting your study materials...

</span>

</div>

) : hasReachedLimit ? (

<div className="flex items-center justify-center space-x-3">

<span className="text-2xl">🚫</span>

<span className="text-lg">

Daily Quota Reached - Visit us tomorrow!

</span>

</div>

):(

<div className="flex items-center justify-center space-x-3">

<span className="text-2xl">⚡</span>

<span className="text-lg">

{user && user.isPremium


? "Generate More Materials"

: "Generate Study Materials"}

</span>

</div>

)}

</button>

{(!user || !user.isPremium) && (

<p className="text-center text-sky-600 text-sm mt-3">

🎉{" "}

{effectiveDailyLimit - userRequestsCount > 0

? `${

effectiveDailyLimit - userRequestsCount

} free searches remaining today!`

: "Your daily quota is done for today!"}

</p>

)}

</div>

</div>

</div>

{/* Error Alert */}

{error && (

<div className="bg-gradient-to-r from-red-50 to-rose-50 border-2 border-red-200 text-red-700 rounded-


2xl p-6 mb-8 shadow-xl shadow-red-100/50 animate-fade-in">

<div className="flex items-start space-x-4">

<div className="w-12 h-12 bg-gradient-to-br from-red-400 to-rose-500 rounded-xl flex items-center


justify-center shadow-lg flex-shrink-0">

<span className="text-white text-2xl">⚠️</span>

</div>

<div>

<h3 className="font-bold text-xl mb-2">Something went wrong</h3>

<p className="text-red-600 leading-relaxed">{error}</p>

</div>

</div>

</div>

)}

{/* Study Notes Section */}

{notes && (
<div className="bg-white/80 backdrop-blur-xl rounded-3xl shadow-2xl shadow-emerald-200/30 border
border-emerald-100 p-10 mb-10 animate-fade-in">

<div className="flex items-center space-x-4 mb-8">

<div className="w-16 h-16 bg-gradient-to-br from-emerald-400 to-teal-500 rounded-2xl flex items-


center justify-center shadow-xl">

<span className="text-3xl">📖</span>

</div>

<div>

<h2 className="text-3xl font-bold text-emerald-700">

Study Notes

</h2>

<p className="text-emerald-600 text-lg">

Deep dive into{" "}

<span className="font-semibold text-sky-600">

{searchedTopic}

</span>

</p>

</div>

</div>

<div

ref={notesRef}

onMouseUp={handleTextSelection}

className="bg-gradient-to-br from-emerald-50 to-teal-50 rounded-2xl p-8 border border-emerald-200


shadow-inner prose prose-lg prose-emerald max-w-none text-emerald-800 leading-relaxed"

>

<ReactMarkdown>{notes}</ReactMarkdown>

</div>

</div>

)}

{/* Selected Text Pop-up */}

{showPopup && selectedText && (

<div

ref={popupRef}

style={{

position: "absolute",

top: popupPosition.y,

left: popupPosition.x,

transform: "translateX(-50%)",

zIndex: 1000,
}}

className="bg-sky-700 text-white py-2 px-4 rounded-xl shadow-lg border border-sky-500 animate-


fade-in-up"

>

<button

onClick={handleAskBuddy}

className="flex items-center space-x-2 text-white font-medium hover:text-sky-200 transition-colors"

>

<span className="text-lg">🧠</span>

<span>Ask Buddy to explain this</span>

</button>

</div>

)}

{/* Video Resources Section */}

{videos.length > 0 && (

<div className="bg-white/80 backdrop-blur-xl rounded-3xl shadow-2xl shadow-rose-200/30 border


border-rose-100 p-10 animate-fade-in">

<div className="flex items-center space-x-4 mb-8">

<div className="w-16 h-16 bg-gradient-to-br from-rose-400 to-pink-500 rounded-2xl flex items-


center justify-center shadow-xl">

<span className="text-3xl">🎥</span>

</div>

<div>

<h2 className="text-3xl font-bold text-rose-700">

Video Resources

</h2>

<p className="text-rose-600 text-lg">

Visual learning about{" "}

<span className="font-semibold text-sky-600">

{searchedTopic}

</span>

</p>

</div>

</div>

<div className="grid gap-6">

{videos.map((video, idx) => (

<a

key={idx}

href={video.url}
target="_blank"

rel="noopener noreferrer"

className="group flex items-center p-6 bg-gradient-to-r from-rose-50 to-pink-50 hover:from-rose-


100 hover:to-pink-100 rounded-2xl border-2 border-rose-200 hover:border-rose-300 transition-all duration-300
transform hover:scale-[1.02] shadow-lg hover:shadow-xl hover:shadow-rose-200/50"

>

<div className="w-16 h-16 bg-gradient-to-br from-red-500 to-rose-600 rounded-xl flex items-


center justify-center mr-6 shadow-lg group-hover:shadow-xl transition-all duration-300">

<svg

className="w-8 h-8 text-white"

fill="currentColor"

viewBox="0 0 20 20"

>

<path

fillRule="evenodd"

d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1


0 000-1.664l-3-2z"

clipRule="evenodd"

/>

</svg>

</div>

<div className="flex-1">

<h3 className="text-rose-800 font-bold text-xl mb-2 group-hover:text-sky-700 transition-colors">

{video.title}

</h3>

<p className="text-rose-600 flex items-center space-x-2">

<span>Watch on YouTube</span>

<svg

className="w-4 h-4"

fill="none"

stroke="currentColor"

viewBox="0 0 24 24"

>

<path

strokeLinecap="round"

strokeLinejoin="round"

strokeWidth={2}

d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"

/>

</svg>

</p>
</div>

</a>

))}

</div>

</div>

)}

</div>

{/* Login Modal */}

{showLoginModal && (

<LoginModal

onLogin={handleDemoLogin}

onClose={() => setShowLoginModal(false)}

/>

)}

{/* Explanation Modal */}

{showExplanationModal && (

<ExplanationModal

explanation={buddyExplanation}

loading={explanationLoading}

onClose={() => setShowExplanationModal(false)}

selectedText={selectedText}

/>

)}

{/* Dummy Payment Modal */}

{showDummyPaymentModal && (

<DummyPaymentModal

onClose={() => setShowDummyPaymentModal(false)}

onSimulateTomorrow={handleSimulateTomorrow}

/>

)}

</div>

);

};

export default StudyBuddy;

You might also like