Spaces:
Runtime error
Runtime error
| import React, { useState, useEffect } from 'react'; | |
| import Header from './components/Header'; | |
| import Hero from './components/Hero'; | |
| import SearchInterface from './components/SearchInterface'; | |
| import ExploreSection from './components/ExploreSection'; | |
| import StudyPlansSection from './components/StudyPlansSection'; | |
| import AIStudyPlanGenerator from './components/AIStudyPlanGenerator'; | |
| import ContentTransformer from './components/ContentTransformer'; | |
| import MultilingualExplorer from './components/MultilingualExplorer'; | |
| import ArticleViewer from './components/ArticleViewer'; | |
| import { StudyPlan, StudyTopic, ProgressData } from './types'; | |
| import { WikimediaAPI } from './utils/wikimedia-api'; | |
| function App() { | |
| const [activeTab, setActiveTab] = useState('hero'); | |
| const [studyPlans, setStudyPlans] = useState<StudyPlan[]>([]); | |
| const [progressData, setProgressData] = useState<ProgressData>({ | |
| studyStreak: 0, | |
| topicsCompleted: 0, | |
| totalStudyTime: 0, | |
| achievements: 0, | |
| weeklyGoal: { current: 0, target: 12 }, | |
| recentActivity: [], | |
| completedTopics: new Set() | |
| }); | |
| // Article viewer state | |
| const [viewingArticle, setViewingArticle] = useState<{ | |
| title: string; | |
| project: string; | |
| content: string; | |
| } | null>(null); | |
| // Load data from localStorage on component mount | |
| useEffect(() => { | |
| const storedPlans = WikimediaAPI.getStoredStudyPlans(); | |
| const storedProgress = WikimediaAPI.getStoredProgress(); | |
| setStudyPlans(storedPlans); | |
| setProgressData({ | |
| ...storedProgress, | |
| completedTopics: new Set(storedProgress.completedTopics || []) | |
| }); | |
| }, []); | |
| // Save data to localStorage whenever it changes | |
| useEffect(() => { | |
| WikimediaAPI.saveStudyPlans(studyPlans); | |
| }, [studyPlans]); | |
| useEffect(() => { | |
| WikimediaAPI.saveProgress({ | |
| ...progressData, | |
| completedTopics: Array.from(progressData.completedTopics) | |
| }); | |
| }, [progressData]); | |
| const handleGetStarted = () => { | |
| setActiveTab('search'); | |
| }; | |
| const handlePlanGenerated = (plan: StudyPlan) => { | |
| setStudyPlans(prev => [...prev, plan]); | |
| setActiveTab('study'); | |
| }; | |
| const handlePlanCreated = (plan: StudyPlan) => { | |
| setStudyPlans(prev => [...prev, plan]); | |
| }; | |
| const handleTopicComplete = (planId: string, topicId: string) => { | |
| // Update study plans | |
| setStudyPlans(prev => prev.map(plan => { | |
| if (plan.id === planId) { | |
| return { | |
| ...plan, | |
| topics: plan.topics.map(topic => { | |
| if (topic.id === topicId) { | |
| return { ...topic, completed: true }; | |
| } | |
| return topic; | |
| }) | |
| }; | |
| } | |
| return plan; | |
| })); | |
| // Update progress data | |
| setProgressData(prev => { | |
| const newCompletedTopics = new Set(prev.completedTopics); | |
| newCompletedTopics.add(topicId); | |
| const plan = studyPlans.find(p => p.id === planId); | |
| const topic = plan?.topics.find(t => t.id === topicId); | |
| const newActivity = { | |
| type: 'completed' as const, | |
| title: topic?.title || 'Unknown Topic', | |
| course: plan?.title || 'Unknown Plan', | |
| time: 'Just now', | |
| icon: 'CheckCircle' as const, | |
| color: 'text-success-600' | |
| }; | |
| // Calculate study streak | |
| const today = new Date().toDateString(); | |
| const lastStudyDate = prev.lastStudyDate; | |
| let newStreak = prev.studyStreak; | |
| if (!lastStudyDate || lastStudyDate !== today) { | |
| const yesterday = new Date(); | |
| yesterday.setDate(yesterday.getDate() - 1); | |
| if (lastStudyDate === yesterday.toDateString()) { | |
| newStreak += 1; | |
| } else if (!lastStudyDate) { | |
| newStreak = 1; | |
| } else { | |
| newStreak = 1; // Reset streak if gap | |
| } | |
| } | |
| const newProgress = { | |
| ...prev, | |
| studyStreak: newStreak, | |
| topicsCompleted: prev.topicsCompleted + 1, | |
| totalStudyTime: prev.totalStudyTime + 2, | |
| weeklyGoal: { | |
| ...prev.weeklyGoal, | |
| current: Math.min(prev.weeklyGoal.current + 2, prev.weeklyGoal.target) | |
| }, | |
| completedTopics: newCompletedTopics, | |
| recentActivity: [newActivity, ...prev.recentActivity.slice(0, 9)], | |
| lastStudyDate: today | |
| }; | |
| // Check for new achievements | |
| const newAchievements = calculateAchievements(newProgress); | |
| newProgress.achievements = newAchievements; | |
| return newProgress; | |
| }); | |
| }; | |
| const handleTopicStart = (planId: string, topicId: string) => { | |
| const plan = studyPlans.find(p => p.id === planId); | |
| const topic = plan?.topics.find(t => t.id === topicId); | |
| const newActivity = { | |
| type: 'started' as const, | |
| title: topic?.title || 'Unknown Topic', | |
| course: plan?.title || 'Unknown Plan', | |
| time: 'Just now', | |
| icon: 'BookOpen' as const, | |
| color: 'text-primary-600' | |
| }; | |
| setProgressData(prev => ({ | |
| ...prev, | |
| recentActivity: [newActivity, ...prev.recentActivity.slice(0, 9)] | |
| })); | |
| }; | |
| const calculateAchievements = (progress: ProgressData): number => { | |
| let count = 0; | |
| if (progress.topicsCompleted >= 1) count++; | |
| if (progress.studyStreak >= 7) count++; | |
| if (progress.topicsCompleted >= 10) count++; | |
| if (progress.totalStudyTime >= 25) count++; | |
| if (progress.topicsCompleted >= 50) count++; | |
| if (progress.studyStreak >= 30) count++; | |
| return count; | |
| }; | |
| const handleViewArticle = (title: string, project: string, content: string) => { | |
| setViewingArticle({ title, project, content }); | |
| }; | |
| const handleBackFromArticle = () => { | |
| setViewingArticle(null); | |
| }; | |
| const handleTabChange = (tab: string) => { | |
| // If viewing an article, go back to the previous tab first | |
| if (viewingArticle) { | |
| setViewingArticle(null); | |
| } | |
| setActiveTab(tab); | |
| }; | |
| const renderContent = () => { | |
| // If viewing an article, show the article viewer | |
| if (viewingArticle) { | |
| return ( | |
| <ArticleViewer | |
| title={viewingArticle.title} | |
| project={viewingArticle.project} | |
| content={viewingArticle.content} | |
| onBack={handleBackFromArticle} | |
| onCreateStudyPlan={(topic) => { | |
| setActiveTab('ai-generator'); | |
| setViewingArticle(null); | |
| }} | |
| onTransformContent={(title, content) => { | |
| setActiveTab('transformer'); | |
| setViewingArticle(null); | |
| }} | |
| /> | |
| ); | |
| } | |
| switch (activeTab) { | |
| case 'search': | |
| return <SearchInterface onViewArticle={handleViewArticle} />; | |
| case 'explore': | |
| return <ExploreSection onViewArticle={handleViewArticle} />; | |
| case 'study': | |
| return ( | |
| <StudyPlansSection | |
| studyPlans={studyPlans} | |
| onTopicComplete={handleTopicComplete} | |
| onTopicStart={handleTopicStart} | |
| onPlanCreated={handlePlanCreated} | |
| onViewArticle={handleViewArticle} | |
| /> | |
| ); | |
| case 'ai-generator': | |
| return <AIStudyPlanGenerator onPlanGenerated={handlePlanGenerated} />; | |
| case 'transformer': | |
| return <ContentTransformer />; | |
| case 'multilingual': | |
| return <MultilingualExplorer onViewArticle={handleViewArticle} />; | |
| default: | |
| return <Hero onGetStarted={handleGetStarted} />; | |
| } | |
| }; | |
| return ( | |
| <div className="min-h-screen bg-gray-50"> | |
| <Header activeTab={activeTab} onTabChange={handleTabChange} /> | |
| <main className="animate-fade-in"> | |
| {renderContent()} | |
| </main> | |
| </div> | |
| ); | |
| } | |
| export default App; |