wiki-project / src /App.tsx
Nagi15's picture
Add codebase
fcb5a67
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;