import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, SafeAreaView, ScrollView, StatusBar, Alert, ActivityIndicator, RefreshControl, Linking, Modal, } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import { useNavigation } from '@react-navigation/native'; import { usePezkuwi } from '../contexts/PezkuwiContext'; import { KurdistanColors } from '../theme/colors'; // Types interface Course { id: number; owner: string; name: string; description: string; content_link: string; status: 'Active' | 'Archived'; created_at: number; } interface Enrollment { student: string; course_id: number; enrolled_at: number; completed_at: number | null; points_earned: number; } // Raw course data from chain toJSON() interface CourseChainData { id: number; owner: string; name: number[] | string; description: number[] | string; contentLink: number[] | string; status: 'Active' | 'Archived'; createdAt: number; } // Raw enrollment data from chain toJSON() interface EnrollmentChainData { student: string; courseId: number; enrolledAt: number; completedAt: number | null; pointsEarned: number; } type TabType = 'courses' | 'enrolled' | 'completed'; const PerwerdeScreen: React.FC = () => { const _navigation = useNavigation(); const { selectedAccount, api, isApiReady } = usePezkuwi(); const isConnected = !!selectedAccount; // State const [activeTab, setActiveTab] = useState('courses'); const [courses, setCourses] = useState([]); const [myEnrollments, setMyEnrollments] = useState([]); const [perwerdeScore, setPerwerdeScore] = useState(0); const [loading, setLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); const [selectedCourse, setSelectedCourse] = useState(null); const [showCourseModal, setShowCourseModal] = useState(false); const [enrolling, setEnrolling] = useState(false); // Fetch all courses from blockchain const fetchCourses = useCallback(async () => { if (!api || !isApiReady) return; try { const entries = await api.query.perwerde.courses.entries(); const courseList: Course[] = []; for (const [_key, value] of entries) { if (!value.isEmpty) { const data = value.toJSON() as unknown as CourseChainData; courseList.push({ id: data.id, owner: data.owner, name: decodeText(data.name), description: decodeText(data.description), content_link: decodeText(data.contentLink), status: data.status, created_at: data.createdAt, }); } } // Sort by id descending (newest first) courseList.sort((a, b) => b.id - a.id); setCourses(courseList); } catch (error) { console.error('Error fetching courses:', error); } }, [api, isApiReady]); // Fetch user's enrollments const fetchMyEnrollments = useCallback(async () => { if (!api || !isApiReady || !selectedAccount) return; try { const studentCourses = await api.query.perwerde.studentCourses(selectedAccount.address); const courseIds = studentCourses.toJSON() as number[]; const enrollmentList: Enrollment[] = []; let totalPoints = 0; for (const courseId of courseIds) { const enrollment = await api.query.perwerde.enrollments([selectedAccount.address, courseId]); if (!enrollment.isEmpty) { const data = enrollment.toJSON() as unknown as EnrollmentChainData; enrollmentList.push({ student: data.student, course_id: data.courseId, enrolled_at: data.enrolledAt, completed_at: data.completedAt, points_earned: data.pointsEarned, }); if (data.completedAt) { totalPoints += data.pointsEarned; } } } setMyEnrollments(enrollmentList); setPerwerdeScore(totalPoints); } catch (error) { console.error('Error fetching enrollments:', error); } }, [api, isApiReady, selectedAccount]); // Helper to decode bounded vec to string const decodeText = (data: number[] | string): string => { if (typeof data === 'string') return data; try { return new TextDecoder().decode(new Uint8Array(data)); } catch { return ''; } }; // Load data const loadData = useCallback(async () => { setLoading(true); await Promise.all([fetchCourses(), fetchMyEnrollments()]); setLoading(false); }, [fetchCourses, fetchMyEnrollments]); // Refresh handler const onRefresh = useCallback(async () => { setRefreshing(true); await loadData(); setRefreshing(false); }, [loadData]); useEffect(() => { if (isConnected && api && isApiReady) { loadData(); } }, [isConnected, api, isApiReady, loadData]); // Check if user is enrolled in a course const isEnrolled = (courseId: number): boolean => { return myEnrollments.some(e => e.course_id === courseId); }; // Check if course is completed const isCompleted = (courseId: number): boolean => { const enrollment = myEnrollments.find(e => e.course_id === courseId); return enrollment?.completed_at !== null && enrollment?.completed_at !== undefined; }; // Get enrollment for a course const getEnrollment = (courseId: number): Enrollment | undefined => { return myEnrollments.find(e => e.course_id === courseId); }; // Enroll in course const handleEnroll = async (courseId: number) => { if (!api || !selectedAccount) { Alert.alert('Xeletî / Error', 'Ji kerema xwe berî têketinê wallet ve girêbidin.'); return; } setEnrolling(true); try { const extrinsic = api.tx.perwerde.enroll(courseId); await new Promise((resolve, reject) => { extrinsic.signAndSend( selectedAccount.address, { signer: selectedAccount.signer }, ({ status, dispatchError }) => { if (dispatchError) { if (dispatchError.isModule) { const decoded = api.registry.findMetaError(dispatchError.asModule); reject(new Error(`${decoded.section}.${decoded.name}`)); } else { reject(new Error(dispatchError.toString())); } return; } if (status.isInBlock || status.isFinalized) { resolve(); } } ); }); Alert.alert( 'Serkeftî! / Success!', 'Tu bi serkeftî tev li kursê bûyî!\n\nYou have successfully enrolled in the course!', [{ text: 'Temam / OK' }] ); // Refresh data await loadData(); setShowCourseModal(false); } catch (error) { console.error('Enrollment error:', error); Alert.alert( 'Xeletî / Error', `Têketin têk çû: ${error instanceof Error ? error.message : 'Unknown error'}`, [{ text: 'Temam / OK' }] ); } finally { setEnrolling(false); } }; // Open IPFS content const openContent = async (ipfsHash: string) => { const url = ipfsHash.startsWith('http') ? ipfsHash : `https://ipfs.io/ipfs/${ipfsHash}`; try { const canOpen = await Linking.canOpenURL(url); if (canOpen) { await Linking.openURL(url); } else { Alert.alert('Xeletî / Error', 'Nikarim linkê vebikum.'); } } catch { Alert.alert('Xeletî / Error', 'Tiştek xelet çû.'); } }; // Filter courses based on active tab const getFilteredCourses = (): Course[] => { switch (activeTab) { case 'courses': return courses.filter(c => c.status === 'Active'); case 'enrolled': return courses.filter(c => isEnrolled(c.id) && !isCompleted(c.id)); case 'completed': return courses.filter(c => isCompleted(c.id)); default: return []; } }; // Render tab button const renderTab = (tab: TabType, label: string, labelKu: string, count: number) => ( setActiveTab(tab)} > {labelKu} {count > 0 && ( {count} )} ); // Render course card const renderCourseCard = (course: Course) => { const enrolled = isEnrolled(course.id); const completed = isCompleted(course.id); const enrollment = getEnrollment(course.id); return ( { setSelectedCourse(course); setShowCourseModal(true); }} activeOpacity={0.7} > {completed ? '✅' : enrolled ? '📖' : '📚'} {course.name} Kurs #{course.id} {completed && enrollment && ( +{enrollment.points_earned} )} {course.description} {completed ? ( Qediya / Completed ) : enrolled ? ( Tev li / Enrolled ) : ( Amade / Available )} ); }; // Not connected view if (!isConnected) { return ( 📚 Perwerde Platforma Perwerdehiya Dijîtal{'\n'}Digital Education Platform Ji kerema xwe wallet ve girêbidin da ku bikaribin kursan bibînin û tev li wan bibin. Please connect your wallet to view and enroll in courses. ); } const filteredCourses = getFilteredCourses(); const enrolledCount = myEnrollments.filter(e => !e.completed_at).length; const completedCount = myEnrollments.filter(e => e.completed_at).length; return ( {/* Score Card */} {perwerdeScore} Puan / Points {completedCount} Qediyayî / Done {enrolledCount} Aktîv / Active {/* Tabs */} {renderTab('courses', 'Courses', 'Kurs', courses.filter(c => c.status === 'Active').length)} {renderTab('enrolled', 'Enrolled', 'Tev li', enrolledCount)} {renderTab('completed', 'Completed', 'Qediya', completedCount)} {/* Content */} } > {loading ? ( Tê barkirin... / Loading... ) : filteredCourses.length === 0 ? ( {activeTab === 'courses' ? '📭' : activeTab === 'enrolled' ? '📋' : '🎓'} {activeTab === 'courses' ? 'Kursek tune / No courses available' : activeTab === 'enrolled' ? 'Tu tev li kursekê nebûyî / Not enrolled in any course' : 'Kursek neqediyaye / No completed courses'} ) : ( {filteredCourses.map(renderCourseCard)} )} {/* Course Detail Modal */} setShowCourseModal(false)} > {selectedCourse && ( <> {selectedCourse.name} setShowCourseModal(false)}> Danasîn / Description {selectedCourse.description} Agahdarî / Info Kurs ID: #{selectedCourse.id} Rewş / Status: {selectedCourse.status === 'Active' ? 'Aktîv' : 'Arşîv'} {isEnrolled(selectedCourse.id) && ( Têketin / Enrolled: {isCompleted(selectedCourse.id) ? 'Qediya ✅' : 'Aktîv 📖'} )} {isCompleted(selectedCourse.id) && ( Puan / Points: +{getEnrollment(selectedCourse.id)?.points_earned || 0} )} {selectedCourse.content_link && ( openContent(selectedCourse.content_link)} > 📄 Naveroka Kursê Veke / Open Course Content )} {!isEnrolled(selectedCourse.id) && selectedCourse.status === 'Active' ? ( handleEnroll(selectedCourse.id)} disabled={enrolling} > {enrolling ? ( ) : ( <> 📝 Tev li Kursê / Enroll )} ) : isCompleted(selectedCourse.id) ? ( 🎓 Te ev kurs qedand! / You completed this course! ) : ( 📖 Tu tev li vê kursê yî / You are enrolled )} )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F5F5F5', }, header: { padding: 20, paddingTop: 16, paddingBottom: 24, flexDirection: 'row', alignItems: 'center', }, backButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(0, 0, 0, 0.15)', justifyContent: 'center', alignItems: 'center', marginRight: 12, }, backButtonText: { fontSize: 24, color: KurdistanColors.reş, fontWeight: 'bold', }, headerContent: { flex: 1, }, headerTitle: { fontSize: 28, fontWeight: 'bold', color: KurdistanColors.reş, }, headerSubtitle: { fontSize: 14, color: KurdistanColors.reş, opacity: 0.8, marginTop: 2, }, scoreCard: { flexDirection: 'row', backgroundColor: KurdistanColors.spi, marginHorizontal: 16, marginTop: -12, borderRadius: 16, padding: 16, boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', elevation: 6, }, scoreItem: { flex: 1, alignItems: 'center', }, scoreValue: { fontSize: 28, fontWeight: 'bold', color: KurdistanColors.zer, }, scoreLabel: { fontSize: 12, color: '#666', marginTop: 4, }, scoreDivider: { width: 1, backgroundColor: '#E0E0E0', marginVertical: 4, }, tabContainer: { flexDirection: 'row', paddingHorizontal: 16, paddingTop: 20, paddingBottom: 8, gap: 8, }, tab: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 10, paddingHorizontal: 12, borderRadius: 12, backgroundColor: '#E0E0E0', gap: 6, }, tabActive: { backgroundColor: KurdistanColors.zer, }, tabText: { fontSize: 13, fontWeight: '600', color: '#666', }, tabTextActive: { color: KurdistanColors.reş, }, tabBadge: { backgroundColor: '#999', borderRadius: 10, paddingHorizontal: 6, paddingVertical: 2, minWidth: 20, alignItems: 'center', }, tabBadgeActive: { backgroundColor: KurdistanColors.reş, }, tabBadgeText: { fontSize: 10, fontWeight: 'bold', color: KurdistanColors.spi, }, tabBadgeTextActive: { color: KurdistanColors.zer, }, content: { flex: 1, padding: 16, }, loadingContainer: { padding: 40, alignItems: 'center', }, loadingText: { marginTop: 12, fontSize: 14, color: '#666', }, emptyContainer: { padding: 60, alignItems: 'center', }, emptyIcon: { fontSize: 64, marginBottom: 16, }, emptyText: { fontSize: 16, color: '#666', textAlign: 'center', }, courseList: { gap: 12, }, courseCard: { backgroundColor: KurdistanColors.spi, borderRadius: 16, padding: 16, boxShadow: '0px 2px 8px rgba(0, 0, 0, 0.08)', elevation: 4, }, courseHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 12, }, courseIcon: { width: 48, height: 48, borderRadius: 12, backgroundColor: '#F0F0F0', justifyContent: 'center', alignItems: 'center', }, courseIconEnrolled: { backgroundColor: '#E3F2FD', }, courseIconCompleted: { backgroundColor: '#E8F5E9', }, courseIconText: { fontSize: 24, }, courseInfo: { flex: 1, marginLeft: 12, }, courseName: { fontSize: 16, fontWeight: '600', color: KurdistanColors.reş, }, courseId: { fontSize: 12, color: '#999', marginTop: 2, }, pointsBadge: { backgroundColor: KurdistanColors.kesk, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, }, pointsText: { fontSize: 12, fontWeight: 'bold', color: KurdistanColors.spi, }, courseDescription: { fontSize: 14, color: '#666', lineHeight: 20, marginBottom: 12, }, courseFooter: { flexDirection: 'row', alignItems: 'center', }, statusAvailable: { fontSize: 12, fontWeight: '600', color: KurdistanColors.yer, }, statusEnrolled: { fontSize: 12, fontWeight: '600', color: '#1976D2', }, statusCompleted: { fontSize: 12, fontWeight: '600', color: KurdistanColors.kesk, }, // Modal styles modalOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'flex-end', }, modalContainer: { backgroundColor: KurdistanColors.spi, borderTopLeftRadius: 24, borderTopRightRadius: 24, maxHeight: '80%', }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 20, borderBottomWidth: 1, borderBottomColor: '#E0E0E0', }, modalTitle: { fontSize: 20, fontWeight: 'bold', color: KurdistanColors.reş, flex: 1, marginRight: 12, }, modalClose: { fontSize: 24, color: '#999', padding: 4, }, modalContent: { padding: 20, }, modalSection: { marginBottom: 20, }, modalSectionTitle: { fontSize: 14, fontWeight: '600', color: '#999', marginBottom: 8, textTransform: 'uppercase', }, modalDescription: { fontSize: 15, color: '#444', lineHeight: 22, }, infoRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#F0F0F0', }, infoLabel: { fontSize: 14, color: '#666', }, infoValue: { fontSize: 14, color: KurdistanColors.reş, fontWeight: '500', }, contentButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#E3F2FD', padding: 14, borderRadius: 12, gap: 8, }, contentButtonIcon: { fontSize: 20, }, contentButtonText: { fontSize: 14, fontWeight: '600', color: '#1976D2', }, modalFooter: { padding: 20, borderTopWidth: 1, borderTopColor: '#E0E0E0', }, enrollButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: KurdistanColors.kesk, padding: 16, borderRadius: 12, gap: 8, }, enrollButtonIcon: { fontSize: 20, }, enrollButtonText: { fontSize: 16, fontWeight: '600', color: KurdistanColors.spi, }, completedBanner: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#E8F5E9', padding: 14, borderRadius: 12, gap: 8, }, completedBannerIcon: { fontSize: 20, }, completedBannerText: { fontSize: 14, fontWeight: '600', color: KurdistanColors.kesk, }, enrolledBanner: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#E3F2FD', padding: 14, borderRadius: 12, gap: 8, }, enrolledBannerIcon: { fontSize: 20, }, enrolledBannerText: { fontSize: 14, fontWeight: '600', color: '#1976D2', }, // Not connected styles notConnectedGradient: { flex: 1, }, notConnectedContent: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 40, }, notConnectedIcon: { fontSize: 80, marginBottom: 20, }, notConnectedTitle: { fontSize: 32, fontWeight: 'bold', color: KurdistanColors.reş, marginBottom: 8, }, notConnectedSubtitle: { fontSize: 16, color: KurdistanColors.reş, textAlign: 'center', opacity: 0.8, marginBottom: 24, }, notConnectedText: { fontSize: 14, color: KurdistanColors.reş, textAlign: 'center', lineHeight: 20, marginBottom: 8, }, notConnectedTextEn: { fontSize: 12, color: KurdistanColors.reş, textAlign: 'center', opacity: 0.7, lineHeight: 18, }, }); export default PerwerdeScreen;