diff --git a/mobile/src/navigation/BottomTabNavigator.tsx b/mobile/src/navigation/BottomTabNavigator.tsx index 34184dc5..59d25d42 100644 --- a/mobile/src/navigation/BottomTabNavigator.tsx +++ b/mobile/src/navigation/BottomTabNavigator.tsx @@ -8,6 +8,7 @@ import DashboardScreen from '../screens/DashboardScreen'; import WalletScreen from '../screens/WalletScreen'; import SwapScreen from '../screens/SwapScreen'; import P2PScreen from '../screens/P2PScreen'; +import EducationScreen from '../screens/EducationScreen'; import BeCitizenScreen from '../screens/BeCitizenScreen'; import ReferralScreen from '../screens/ReferralScreen'; import ProfileScreen from '../screens/ProfileScreen'; @@ -17,6 +18,7 @@ export type BottomTabParamList = { Wallet: undefined; Swap: undefined; P2P: undefined; + Education: undefined; BeCitizen: undefined; Referral: undefined; Profile: undefined; @@ -98,6 +100,18 @@ const BottomTabNavigator: React.FC = () => { }} /> + ( + + {focused ? '🎓' : '📚'} + + ), + }} + /> + { + const { t } = useTranslation(); + const { api, isApiReady, selectedAccount, getKeyPair } = usePolkadot(); + + const [activeTab, setActiveTab] = useState('all'); + const [courses, setCourses] = useState([]); + const [enrollments, setEnrollments] = useState([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [enrolling, setEnrolling] = useState(null); + + const fetchCourses = useCallback(async () => { + if (!api || !isApiReady) return; + + try { + setLoading(true); + const allCourses = await getAllCourses(api); + setCourses(allCourses); + } catch (error) { + console.error('Failed to fetch courses:', error); + } finally { + setLoading(false); + setRefreshing(false); + } + }, [api, isApiReady]); + + const fetchEnrollments = useCallback(async () => { + if (!selectedAccount) { + setEnrollments([]); + return; + } + + try { + const studentEnrollments = await getStudentEnrollments(selectedAccount.address); + setEnrollments(studentEnrollments); + } catch (error) { + console.error('Failed to fetch enrollments:', error); + } + }, [selectedAccount]); + + useEffect(() => { + fetchCourses(); + fetchEnrollments(); + }, [fetchCourses, fetchEnrollments]); + + const handleRefresh = () => { + setRefreshing(true); + fetchCourses(); + fetchEnrollments(); + }; + + const handleEnroll = async (courseId: number) => { + if (!api || !selectedAccount) { + Alert.alert('Error', 'Please connect your wallet'); + return; + } + + try { + setEnrolling(courseId); + + const keyPair = await getKeyPair(selectedAccount.address); + if (!keyPair) { + throw new Error('Failed to load keypair'); + } + + await enrollInCourse(api, { + address: selectedAccount.address, + meta: {}, + type: 'sr25519', + } as any, courseId); + + Alert.alert('Success', 'Successfully enrolled in course!'); + fetchEnrollments(); + } catch (error: any) { + console.error('Enrollment failed:', error); + Alert.alert('Enrollment Failed', error.message || 'Failed to enroll in course'); + } finally { + setEnrolling(null); + } + }; + + const handleCompleteCourse = async (courseId: number) => { + if (!api || !selectedAccount) { + Alert.alert('Error', 'Please connect your wallet'); + return; + } + + Alert.alert( + 'Complete Course', + 'Are you sure you want to mark this course as completed?', + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Complete', + onPress: async () => { + try { + const keyPair = await getKeyPair(selectedAccount.address); + if (!keyPair) { + throw new Error('Failed to load keypair'); + } + + await completeCourse(api, { + address: selectedAccount.address, + meta: {}, + type: 'sr25519', + } as any, courseId); + + Alert.alert('Success', 'Course completed! Certificate issued.'); + fetchEnrollments(); + } catch (error: any) { + console.error('Completion failed:', error); + Alert.alert('Error', error.message || 'Failed to complete course'); + } + }, + }, + ] + ); + }; + + const isEnrolled = (courseId: number) => { + return enrollments.some((e) => e.course_id === courseId); + }; + + const isCompleted = (courseId: number) => { + return enrollments.some((e) => e.course_id === courseId && e.is_completed); + }; + + const getEnrollmentProgress = (courseId: number) => { + const enrollment = enrollments.find((e) => e.course_id === courseId); + return enrollment?.points_earned || 0; + }; + + const renderCourseCard = ({ item }: { item: Course }) => { + const enrolled = isEnrolled(item.id); + const completed = isCompleted(item.id); + const progress = getEnrollmentProgress(item.id); + const isEnrollingThis = enrolling === item.id; + + return ( + + {/* Course Header */} + + + 📚 + + + {item.name} + + By: {item.owner.slice(0, 6)}...{item.owner.slice(-4)} + + + {completed && ( + + )} + {enrolled && !completed && ( + + )} + + + {/* Course Description */} + + {item.description} + + + {/* Progress (if enrolled) */} + {enrolled && !completed && ( + + Progress + + + + {progress} points + + )} + + {/* Course Metadata */} + + + 🎓 + Certificate upon completion + + + 📅 + + Created: {new Date(item.created_at).toLocaleDateString()} + + + + + {/* Action Button */} + {!enrolled && ( + + )} + + {enrolled && !completed && ( + + )} + + {completed && ( + + )} + + ); + }; + + const displayCourses = + activeTab === 'all' + ? courses + : courses.filter((c) => isEnrolled(c.id)); + + const renderEmptyState = () => ( + + + {activeTab === 'all' ? '📚' : '🎓'} + + + {activeTab === 'all' ? 'No Courses Available' : 'No Enrolled Courses'} + + + {activeTab === 'all' + ? 'Check back later for new courses' + : 'Browse available courses and enroll to start learning'} + + {activeTab === 'my-courses' && ( + + )} + + ); + + return ( + + {/* Header */} + + + Perwerde 🎓 + Decentralized Education Platform + + + + {/* Connection Warning */} + {!isApiReady && ( + + Connecting to blockchain... + + )} + + {/* Tabs */} + + setActiveTab('all')} + > + + All Courses + + + + setActiveTab('my-courses')} + > + + My Courses ({enrollments.length}) + + + + + {/* Course List */} + {loading && !refreshing ? ( + + + Loading courses... + + ) : ( + item.id.toString()} + contentContainerStyle={styles.listContent} + ListEmptyComponent={renderEmptyState} + refreshControl={ + + } + /> + )} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: AppColors.background, + }, + header: { + padding: 16, + paddingBottom: 12, + }, + title: { + fontSize: 28, + fontWeight: '700', + color: '#000', + marginBottom: 4, + }, + subtitle: { + fontSize: 14, + color: '#666', + }, + warningBanner: { + backgroundColor: '#FFF3CD', + padding: 12, + marginHorizontal: 16, + marginBottom: 12, + borderRadius: 8, + borderWidth: 1, + borderColor: '#FFE69C', + }, + warningText: { + fontSize: 14, + color: '#856404', + textAlign: 'center', + }, + tabs: { + flexDirection: 'row', + paddingHorizontal: 16, + borderBottomWidth: 1, + borderBottomColor: '#E0E0E0', + marginBottom: 16, + }, + tab: { + flex: 1, + paddingVertical: 12, + alignItems: 'center', + borderBottomWidth: 2, + borderBottomColor: 'transparent', + }, + activeTab: { + borderBottomColor: KurdistanColors.kesk, + }, + tabText: { + fontSize: 16, + fontWeight: '600', + color: '#666', + }, + activeTabText: { + color: KurdistanColors.kesk, + }, + listContent: { + padding: 16, + paddingTop: 0, + }, + courseCard: { + padding: 16, + marginBottom: 16, + }, + courseHeader: { + flexDirection: 'row', + alignItems: 'flex-start', + marginBottom: 12, + }, + courseIcon: { + width: 56, + height: 56, + borderRadius: 12, + backgroundColor: '#F0F9F4', + justifyContent: 'center', + alignItems: 'center', + }, + courseIconText: { + fontSize: 28, + }, + courseInfo: { + flex: 1, + marginLeft: 12, + }, + courseName: { + fontSize: 18, + fontWeight: '700', + color: '#000', + marginBottom: 4, + }, + courseInstructor: { + fontSize: 14, + color: '#666', + }, + courseDescription: { + fontSize: 14, + color: '#666', + lineHeight: 20, + marginBottom: 16, + }, + progressContainer: { + marginBottom: 16, + }, + progressLabel: { + fontSize: 12, + color: '#666', + marginBottom: 8, + }, + progressBar: { + height: 8, + backgroundColor: '#E0E0E0', + borderRadius: 4, + overflow: 'hidden', + marginBottom: 4, + }, + progressFill: { + height: '100%', + backgroundColor: KurdistanColors.kesk, + borderRadius: 4, + }, + progressText: { + fontSize: 12, + color: KurdistanColors.kesk, + fontWeight: '600', + }, + courseMetadata: { + borderTopWidth: 1, + borderTopColor: '#F0F0F0', + paddingTop: 12, + marginBottom: 16, + }, + metadataItem: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 8, + }, + metadataIcon: { + fontSize: 16, + marginRight: 8, + }, + metadataText: { + fontSize: 12, + color: '#666', + }, + enrollButton: { + marginTop: 8, + }, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + loadingText: { + marginTop: 12, + fontSize: 14, + color: '#666', + }, + emptyState: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingVertical: 60, + }, + emptyIcon: { + fontSize: 64, + marginBottom: 16, + }, + emptyTitle: { + fontSize: 20, + fontWeight: '700', + color: '#000', + marginBottom: 8, + }, + emptyText: { + fontSize: 14, + color: '#666', + textAlign: 'center', + marginBottom: 24, + paddingHorizontal: 32, + }, + browseButton: { + minWidth: 150, + }, +}); + +export default EducationScreen;