feat: add University, Certificates (Perwerde), Research education pages

- UniversityPage (/education/university): static course catalog, 6 courses, level badges, enroll redirects to Perwerde
- CertificatesPage (/education/certificates): blockchain-connected Perwerde platform, 3 tabs (Kurs/Tev li/Qediya), real api.query.perwerde + api.tx.perwerde.enroll, score tracker
- ResearchPage (/education/research): 5 academic papers, expandable abstracts, status badges, submit CTA
- Wire routes in App.tsx, remove comingSoon from all 3 in MobileHomeLayout
- Add university.*, certificates.*, research.* i18n keys to en.ts
This commit is contained in:
2026-04-09 08:51:21 +03:00
parent c56e021a6b
commit 892c78324f
6 changed files with 734 additions and 3 deletions
+6
View File
@@ -72,6 +72,9 @@ const PollsPage = lazy(() => import('@/pages/governance/PollsPage'));
const WhatsKURDPage = lazy(() => import('@/pages/social/WhatsKURDPage'));
const KurdMediaPage = lazy(() => import('@/pages/social/KurdMediaPage'));
const HelpPage = lazy(() => import('@/pages/HelpPage'));
const UniversityPage = lazy(() => import('@/pages/education/UniversityPage'));
const CertificatesPage = lazy(() => import('@/pages/education/CertificatesPage'));
const ResearchPage = lazy(() => import('@/pages/education/ResearchPage'));
// Network pages
const Mainnet = lazy(() => import('@/pages/networks/Mainnet'));
@@ -242,6 +245,9 @@ function App() {
<Route path="/social/whatskurd" element={<WhatsKURDPage />} />
<Route path="/social/kurdmedia" element={<KurdMediaPage />} />
<Route path="/help" element={<HelpPage />} />
<Route path="/education/university" element={<UniversityPage />} />
<Route path="/education/certificates" element={<CertificatesPage />} />
<Route path="/education/research" element={<ResearchPage />} />
<Route path="/presale" element={<Presale />} />
<Route path="/launchpad" element={<PresaleList />} />
<Route path="/launchpad/:id" element={<PresaleDetail />} />
+3 -3
View File
@@ -96,10 +96,10 @@ const APP_SECTIONS: AppSection[] = [
emoji: '📚',
borderColor: 'border-l-yellow-500',
apps: [
{ title: 'mobile.app.university', icon: '🎓', route: '/education', comingSoon: true },
{ title: 'mobile.app.university', icon: '🎓', route: '/education/university' },
{ title: 'mobile.app.perwerde', icon: '📖', route: '/education', requiresAuth: true },
{ title: 'mobile.app.certificates', icon: '🏆', route: '/education', comingSoon: true },
{ title: 'mobile.app.research', icon: '🔬', route: '/education', comingSoon: true },
{ title: 'mobile.app.certificates', icon: '🏆', route: '/education/certificates' },
{ title: 'mobile.app.research', icon: '🔬', route: '/education/research' },
],
},
];
+49
View File
@@ -3990,4 +3990,53 @@ export default {
'help.feature.community': 'Community Contact',
'help.whatskurd.title': 'WhatsKURD Messaging',
'help.whatskurd.desc': 'Contact us via the blockchain messaging system',
// University page
'university.title': 'Zanîngeha Dijîtal',
'university.subtitle': 'Kurdistan Digital University',
'university.breadcrumb': 'Education',
'university.stats.courses': 'Kurs / Courses',
'university.stats.students': 'Xwendekar / Students',
'university.reward': '🎁 Xelat / Reward:',
'university.enroll': 'Tomar bibe / Enroll',
// Certificates / Perwerde page
'certificates.title': 'Perwerde',
'certificates.subtitle': 'Digital Education Platform',
'certificates.breadcrumb': 'Education',
'certificates.tab.courses': 'Kurs',
'certificates.tab.enrolled': 'Tev li',
'certificates.tab.completed': 'Qediya',
'certificates.stats.points': 'Puan / Points',
'certificates.stats.done': 'Qediyayî / Done',
'certificates.stats.active': 'Aktîv / Active',
'certificates.noWallet.title': 'Perwerde',
'certificates.noWallet.desc': 'Ji kerema xwe wallet ve girêbidin da ku bikaribin kursan bibînin û tev li wan bibin.',
'certificates.noWallet.en': 'Please connect your wallet to view and enroll in courses.',
'certificates.loading': 'Tê barkirin... / Loading...',
'certificates.empty.courses': 'Kursek tune / No courses available',
'certificates.empty.enrolled': 'Tu tev li kursekê nebûyî / Not enrolled in any course',
'certificates.empty.completed': 'Kursek neqediyaye / No completed courses',
'certificates.course.status': 'Rewş / Status',
'certificates.course.enrollment': 'Têketin / Enrolled',
'certificates.course.points': 'Puan / Points',
'certificates.course.openContent': 'Naveroka Kursê Veke / Open Course Content',
'certificates.enroll': 'Tev li Kursê / Enroll',
'certificates.completedBanner': 'Te ev kurs qedand! / You completed this course!',
'certificates.enrolledBanner': 'Tu tev li vê kursê yî / You are enrolled',
// Research page
'research.title': 'Navenda Lêkolînê',
'research.subtitle': 'Research Center',
'research.breadcrumb': 'Education',
'research.paperCount': '{{count}} lêkolîn / papers',
'research.about.title': 'Der barê Navenda Lêkolînê / About',
'research.about.ku': 'Navenda Lêkolînê gotarên zanistî yên li ser aboriya dijîtal a Kurdistanê, teknolojiya blockchain, û mijarên pêwendîdar berhev dike.',
'research.about.en': "The Research Center curates academic papers on Kurdistan's digital economy, blockchain technology, and related topics.",
'research.papers.title': 'Lêkolîn / Papers',
'research.more': 'Bêtir / More',
'research.less': 'Kêmtir / Less',
'research.submit.title': 'Lêkolîna xwe bişînin / Submit your research',
'research.submit.desc': 'Hûn dikarin gotarên xwe yên zanistî ji bo vekolînê bişînin. Submit your academic papers for peer review.',
'research.submit.btn': 'Bişîne / Submit',
}
@@ -0,0 +1,341 @@
import { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { usePezkuwi } from '@/contexts/PezkuwiContext';
interface Course {
id: number;
owner: string;
name: string;
description: string;
contentLink: string;
status: 'Active' | 'Archived';
}
interface Enrollment {
courseId: number;
enrolledAt: number;
completedAt: number | null;
pointsEarned: number;
}
type Tab = 'courses' | 'enrolled' | 'completed';
interface CourseChainData {
id: number; owner: string;
name: number[] | string; description: number[] | string;
contentLink: number[] | string;
status: 'Active' | 'Archived';
}
interface EnrollmentChainData {
student: string; courseId: number; enrolledAt: number;
completedAt: number | null; pointsEarned: number;
}
function decodeText(data: number[] | string): string {
if (typeof data === 'string') return data;
try { return new TextDecoder().decode(new Uint8Array(data)); } catch { return ''; }
}
export default function CertificatesPage() {
const navigate = useNavigate();
const { t } = useTranslation();
const { api, isApiReady, selectedAccount, getKeyPair } = usePezkuwi();
const [tab, setTab] = useState<Tab>('courses');
const [courses, setCourses] = useState<Course[]>([]);
const [enrollments, setEnrollments] = useState<Enrollment[]>([]);
const [score, setScore] = useState(0);
const [loading, setLoading] = useState(false);
const [enrolling, setEnrolling] = useState<number | null>(null);
const [expandedCourse, setExpandedCourse] = useState<Course | null>(null);
const [error, setError] = useState<string | null>(null);
const fetchCourses = useCallback(async () => {
if (!api || !isApiReady) return;
try {
const entries = await api.query.perwerde.courses.entries();
const list: Course[] = [];
for (const [, value] of entries) {
if (!value.isEmpty) {
const d = value.toJSON() as unknown as CourseChainData;
list.push({ id: d.id, owner: d.owner, name: decodeText(d.name), description: decodeText(d.description), contentLink: decodeText(d.contentLink), status: d.status });
}
}
list.sort((a, b) => b.id - a.id);
setCourses(list);
} catch (e) { console.error('fetchCourses', e); }
}, [api, isApiReady]);
const fetchEnrollments = useCallback(async () => {
if (!api || !isApiReady || !selectedAccount) return;
try {
const ids = (await api.query.perwerde.studentCourses(selectedAccount.address)).toJSON() as number[];
const list: Enrollment[] = [];
let pts = 0;
for (const cid of ids) {
const e = await api.query.perwerde.enrollments([selectedAccount.address, cid]);
if (!e.isEmpty) {
const d = e.toJSON() as unknown as EnrollmentChainData;
list.push({ courseId: d.courseId, enrolledAt: d.enrolledAt, completedAt: d.completedAt, pointsEarned: d.pointsEarned });
if (d.completedAt) pts += d.pointsEarned;
}
}
setEnrollments(list);
setScore(pts);
} catch (e) { console.error('fetchEnrollments', e); }
}, [api, isApiReady, selectedAccount]);
const load = useCallback(async () => {
setLoading(true);
await Promise.all([fetchCourses(), fetchEnrollments()]);
setLoading(false);
}, [fetchCourses, fetchEnrollments]);
useEffect(() => {
if (isApiReady) load();
}, [isApiReady, selectedAccount, load]);
const isEnrolled = (id: number) => enrollments.some(e => e.courseId === id);
const isCompleted = (id: number) => enrollments.find(e => e.courseId === id)?.completedAt != null;
const getEnrollment = (id: number) => enrollments.find(e => e.courseId === id);
const handleEnroll = async (courseId: number) => {
if (!api || !selectedAccount) return;
setEnrolling(courseId);
setError(null);
try {
const kp = await getKeyPair(selectedAccount.address);
if (!kp) throw new Error('Could not get signer');
await new Promise<void>((resolve, reject) => {
api.tx.perwerde.enroll(courseId).signAndSend(kp, ({ status, dispatchError }) => {
if (dispatchError) {
const msg = dispatchError.isModule
? (() => { const d = api.registry.findMetaError(dispatchError.asModule); return `${d.section}.${d.name}`; })()
: dispatchError.toString();
reject(new Error(msg)); return;
}
if (status.isInBlock || status.isFinalized) resolve();
});
});
setExpandedCourse(null);
await load();
} catch (e) {
setError(e instanceof Error ? e.message : 'Unknown error');
} finally {
setEnrolling(null);
}
};
const filteredCourses = courses.filter(c => {
if (tab === 'courses') return c.status === 'Active';
if (tab === 'enrolled') return isEnrolled(c.id) && !isCompleted(c.id);
return isCompleted(c.id);
});
const enrolledCount = enrollments.filter(e => !e.completedAt).length;
const completedCount = enrollments.filter(e => e.completedAt != null).length;
const TABS: { key: Tab; label: string; count: number }[] = [
{ key: 'courses', label: t('certificates.tab.courses', 'Kurs'), count: courses.filter(c => c.status === 'Active').length },
{ key: 'enrolled', label: t('certificates.tab.enrolled', 'Tev li'), count: enrolledCount },
{ key: 'completed', label: t('certificates.tab.completed', 'Qediya'), count: completedCount },
];
return (
<div className="min-h-screen bg-gray-950 text-white flex flex-col">
{/* Header */}
<div className="bg-yellow-600 px-4 pt-4 pb-3">
<div className="flex items-center gap-3 mb-3">
<button onClick={() => navigate(-1)} className="text-white/80 hover:text-white text-xl leading-none"></button>
<span className="text-sm text-white/70">{t('certificates.breadcrumb', 'Education')}</span>
</div>
<div className="text-center mb-3">
<span className="text-4xl block mb-1">🏆</span>
<h1 className="text-xl font-bold">{t('certificates.title', 'Perwerde')}</h1>
<p className="text-white/70 text-xs mt-0.5">{t('certificates.subtitle', 'Digital Education Platform')}</p>
</div>
{/* Score bar */}
{selectedAccount && (
<div className="flex justify-around bg-white/15 rounded-xl py-2.5 mx-2">
<div className="text-center">
<p className="text-lg font-bold text-yellow-300">{score}</p>
<p className="text-[10px] text-white/70">{t('certificates.stats.points', 'Puan / Points')}</p>
</div>
<div className="w-px bg-white/30" />
<div className="text-center">
<p className="text-lg font-bold">{completedCount}</p>
<p className="text-[10px] text-white/70">{t('certificates.stats.done', 'Qediyayî / Done')}</p>
</div>
<div className="w-px bg-white/30" />
<div className="text-center">
<p className="text-lg font-bold">{enrolledCount}</p>
<p className="text-[10px] text-white/70">{t('certificates.stats.active', 'Aktîv / Active')}</p>
</div>
</div>
)}
</div>
{/* No wallet */}
{!selectedAccount && (
<div className="flex-1 flex flex-col items-center justify-center px-8 py-12 text-center">
<span className="text-6xl mb-4">📚</span>
<p className="font-bold text-lg mb-2">{t('certificates.noWallet.title', 'Perwerde')}</p>
<p className="text-sm text-gray-400 mb-1">{t('certificates.noWallet.desc', 'Ji kerema xwe wallet ve girêbidin da ku bikaribin kursan bibînin û tev li wan bibin.')}</p>
<p className="text-xs text-gray-500">{t('certificates.noWallet.en', 'Please connect your wallet to view and enroll in courses.')}</p>
</div>
)}
{selectedAccount && (
<>
{/* Tabs */}
<div className="flex gap-2 px-4 pt-3 pb-1">
{TABS.map(tb => (
<button
key={tb.key}
onClick={() => setTab(tb.key)}
className={`flex-1 flex items-center justify-center gap-1.5 py-2 px-3 rounded-xl text-xs font-semibold transition-colors ${
tab === tb.key ? 'bg-yellow-500 text-gray-900' : 'bg-gray-800 text-gray-400'
}`}
>
{tb.label}
{tb.count > 0 && (
<span className={`rounded-full px-1.5 py-0.5 text-[10px] font-bold ${tab === tb.key ? 'bg-gray-900 text-yellow-400' : 'bg-gray-600 text-white'}`}>
{tb.count}
</span>
)}
</button>
))}
</div>
{/* Error */}
{error && (
<div className="mx-4 mt-2 bg-red-900/30 border border-red-700 rounded-xl p-3 text-xs text-red-300">
{error}
</div>
)}
{/* Course list */}
<div className="flex-1 overflow-y-auto px-4 py-3 space-y-3 max-w-lg mx-auto w-full">
{loading && (
<div className="text-center py-12 text-gray-500">
<div className="text-3xl mb-3 animate-spin"></div>
<p className="text-sm">{t('certificates.loading', 'Tê barkirin... / Loading...')}</p>
</div>
)}
{!loading && filteredCourses.length === 0 && (
<div className="text-center py-12 text-gray-500">
<p className="text-4xl mb-3">{tab === 'completed' ? '🎓' : tab === 'enrolled' ? '📋' : '📭'}</p>
<p className="text-sm">
{tab === 'courses' ? t('certificates.empty.courses', 'Kursek tune / No courses available')
: tab === 'enrolled' ? t('certificates.empty.enrolled', 'Tu tev li kursekê nebûyî / Not enrolled in any course')
: t('certificates.empty.completed', 'Kursek neqediyaye / No completed courses')}
</p>
</div>
)}
{!loading && filteredCourses.map(course => {
const enrolled = isEnrolled(course.id);
const completed = isCompleted(course.id);
const enroll = getEnrollment(course.id);
const isExpanded = expandedCourse?.id === course.id;
return (
<div key={course.id} className="bg-gray-900 rounded-2xl p-4">
<div
className="flex items-center gap-3 cursor-pointer"
onClick={() => setExpandedCourse(isExpanded ? null : course)}
>
<div className={`w-12 h-12 rounded-xl flex items-center justify-center text-2xl flex-shrink-0 ${
completed ? 'bg-green-900/50' : enrolled ? 'bg-blue-900/50' : 'bg-gray-800'
}`}>
{completed ? '✅' : enrolled ? '📖' : '📚'}
</div>
<div className="flex-1 min-w-0">
<p className="font-semibold text-sm text-white truncate">{course.name}</p>
<p className="text-[11px] text-gray-500">Kurs #{course.id}</p>
</div>
{completed && enroll && (
<span className="text-[11px] font-bold text-white bg-green-700 px-2 py-0.5 rounded-full flex-shrink-0">
+{enroll.pointsEarned}
</span>
)}
<span className="text-gray-600 text-sm">{isExpanded ? '▲' : '▼'}</span>
</div>
{isExpanded && (
<div className="mt-3 pt-3 border-t border-gray-700 space-y-3">
{course.description && (
<p className="text-xs text-gray-300 leading-relaxed">{course.description}</p>
)}
<div className="space-y-1.5 text-xs">
<div className="flex justify-between">
<span className="text-gray-500">{t('certificates.course.status', 'Rewş / Status')}</span>
<span className={course.status === 'Active' ? 'text-green-400' : 'text-gray-400'}>
{course.status === 'Active' ? 'Aktîv' : 'Arşîv'}
</span>
</div>
{enrolled && (
<div className="flex justify-between">
<span className="text-gray-500">{t('certificates.course.enrollment', 'Têketin / Enrolled')}</span>
<span className={completed ? 'text-green-400' : 'text-blue-400'}>
{completed ? 'Qediya ✅' : 'Aktîv 📖'}
</span>
</div>
)}
{completed && enroll && (
<div className="flex justify-between">
<span className="text-gray-500">{t('certificates.course.points', 'Puan / Points')}</span>
<span className="text-yellow-400 font-bold">+{enroll.pointsEarned}</span>
</div>
)}
</div>
{course.contentLink && (
<a
href={course.contentLink.startsWith('http') ? course.contentLink : `https://ipfs.io/ipfs/${course.contentLink}`}
target="_blank" rel="noopener noreferrer"
className="flex items-center justify-center gap-2 bg-blue-900/30 border border-blue-700/40 rounded-xl py-2.5 text-xs text-blue-300 font-semibold"
>
📄 {t('certificates.course.openContent', 'Naveroka Kursê Veke / Open Course Content')}
</a>
)}
{!enrolled && course.status === 'Active' && (
<button
className="w-full bg-yellow-600 hover:bg-yellow-500 text-white rounded-xl py-2.5 text-sm font-semibold flex items-center justify-center gap-2 transition-colors disabled:opacity-50"
disabled={enrolling === course.id}
onClick={() => handleEnroll(course.id)}
>
{enrolling === course.id ? (
<span className="animate-spin"></span>
) : (
<>📝 {t('certificates.enroll', 'Tev li Kursê / Enroll')}</>
)}
</button>
)}
{completed && (
<div className="flex items-center justify-center gap-2 bg-green-900/30 border border-green-700/40 rounded-xl py-2.5 text-xs text-green-300 font-semibold">
🎓 {t('certificates.completedBanner', 'Te ev kurs qedand! / You completed this course!')}
</div>
)}
{enrolled && !completed && (
<div className="flex items-center justify-center gap-2 bg-blue-900/30 border border-blue-700/40 rounded-xl py-2.5 text-xs text-blue-300 font-semibold">
📖 {t('certificates.enrolledBanner', 'Tu tev li vê kursê yî / You are enrolled')}
</div>
)}
</div>
)}
</div>
);
})}
</div>
</>
)}
<div className="h-10" />
</div>
);
}
+170
View File
@@ -0,0 +1,170 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
interface Paper {
id: string;
emoji: string;
titleKu: string;
title: string;
authors: string;
abstract: string;
category: string;
date: string;
citations: number;
status: 'published' | 'peer-review' | 'draft';
}
const PAPERS: Paper[] = [
{
id: '1', emoji: '🔗',
titleKu: 'Blockchain ji bo Aboriya Dijîtal a Kurdistanê',
title: 'Blockchain for Kurdistan Digital Economy',
authors: 'Dr. A. Kurdo, M. Ehmed',
abstract: 'This paper explores the potential of blockchain technology in building a decentralized digital economy for Kurdistan. We propose the Pezkuwi consensus mechanism and analyze its throughput, security, and decentralization trade-offs.',
category: 'Blockchain', date: '2026-02-15', citations: 42, status: 'published',
},
{
id: '2', emoji: '💱',
titleKu: 'Tokenomiya HEZ: Modela Aborî ya Nenavendî',
title: 'HEZ Tokenomics: A Decentralized Economic Model',
authors: 'Prof. R. Xan, S. Demirtash',
abstract: 'An analysis of the HEZ token economic model including supply dynamics, staking incentives, governance utility, and long-term sustainability. We model inflation, deflation, and equilibrium scenarios.',
category: 'Economics', date: '2026-01-20', citations: 31, status: 'published',
},
{
id: '3', emoji: '🏘️',
titleKu: 'Bereketli: Aboriya Taxê ya Dijîtal',
title: 'Bereketli: Digital Neighborhood Economy',
authors: 'K. Zana, B. Shêx',
abstract: 'We present Bereketli, a peer-to-peer neighborhood economy platform built on blockchain. The system enables local trade, micro-lending, and community resource sharing with minimal trust assumptions.',
category: 'DeFi / Social', date: '2025-11-30', citations: 18, status: 'published',
},
{
id: '4', emoji: '🗣️',
titleKu: 'NLP ji bo Zimanê Kurdî: Rewş û Derfet',
title: 'NLP for Kurdish Language: Status and Opportunities',
authors: 'Dr. J. Bakir, D. Ehmed',
abstract: 'A comprehensive survey of Natural Language Processing research for the Kurdish language. We identify key gaps in tokenization, machine translation, and sentiment analysis, and propose a community-driven dataset initiative.',
category: 'AI / Language', date: '2026-03-05', citations: 8, status: 'peer-review',
},
{
id: '5', emoji: '🔐',
titleKu: 'Nasnameya Dijîtal a Nenavendî li ser Pezkuwi',
title: 'Decentralized Digital Identity on Pezkuwi',
authors: 'M. Baran, A. Kurdo',
abstract: 'We design a self-sovereign identity (SSI) framework for the Pezkuwi network. Citizens control their own credentials using zero-knowledge proofs while maintaining compliance with governance requirements.',
category: 'Identity / Privacy', date: '2026-03-20', citations: 3, status: 'peer-review',
},
];
const STATUS_META = {
'published': { label: 'Weşandî / Published', color: '#16a34a', bg: '#16a34a22' },
'peer-review': { label: 'Peer Review', color: '#b45309', bg: '#f59e0b33' },
'draft': { label: 'Draft', color: '#6b7280', bg: '#6b728022' },
};
export default function ResearchPage() {
const navigate = useNavigate();
const { t } = useTranslation();
const [expandedId, setExpandedId] = useState<string | null>(null);
return (
<div className="min-h-screen bg-gray-950 text-white">
{/* Header */}
<div className="bg-green-700 px-4 pt-4 pb-5">
<div className="flex items-center gap-3 mb-4">
<button onClick={() => navigate(-1)} className="text-white/80 hover:text-white text-xl leading-none"></button>
<span className="text-sm text-white/70">{t('research.breadcrumb', 'Education')}</span>
</div>
<div className="text-center">
<span className="text-5xl block mb-2">🔬</span>
<h1 className="text-2xl font-bold">{t('research.title', 'Navenda Lêkolînê')}</h1>
<p className="text-white/70 text-sm mt-0.5">{t('research.subtitle', 'Research Center')}</p>
<p className="text-yellow-400 text-xs mt-2 font-semibold">
{t('research.paperCount', '{{count}} lêkolîn / papers', { count: PAPERS.length })}
</p>
</div>
</div>
<div className="px-4 py-4 space-y-3 max-w-lg mx-auto">
{/* About card */}
<div className="bg-gray-900 rounded-2xl p-4">
<p className="text-sm font-bold text-white mb-2">{t('research.about.title', 'Der barê Navenda Lêkolînê / About')}</p>
<p className="text-xs text-gray-300 leading-relaxed mb-1">
{t('research.about.ku', 'Navenda Lêkolînê gotarên zanistî yên li ser aboriya dijîtal a Kurdistanê, teknolojiya blockchain, û mijarên pêwendîdar berhev dike.')}
</p>
<p className="text-xs text-gray-500 leading-relaxed">
{t('research.about.en', 'The Research Center curates academic papers on Kurdistan\'s digital economy, blockchain technology, and related topics.')}
</p>
</div>
{/* Papers */}
<p className="text-sm font-bold text-white px-1">{t('research.papers.title', 'Lêkolîn / Papers')}</p>
{PAPERS.map(paper => {
const meta = STATUS_META[paper.status];
const isOpen = expandedId === paper.id;
return (
<button
key={paper.id}
className="w-full text-left bg-gray-900 rounded-2xl p-4"
onClick={() => setExpandedId(isOpen ? null : paper.id)}
>
{/* Header row */}
<div className="flex items-center justify-between mb-2">
<span className="text-2xl">{paper.emoji}</span>
<div className="flex items-center gap-2">
<span
className="text-[10px] font-semibold px-2 py-0.5 rounded-full"
style={{ color: meta.color, backgroundColor: meta.bg }}
>
{meta.label}
</span>
<span className="text-[11px] text-gray-500">📖 {paper.citations}</span>
</div>
</div>
<p className="font-bold text-white text-sm leading-snug">{paper.titleKu}</p>
<p className="text-xs text-gray-400 mt-0.5 mb-1">{paper.title}</p>
<p className="text-xs text-blue-400 mb-2">{paper.authors}</p>
<div className="flex gap-4 mb-2">
<span className="text-[11px] text-gray-500">📁 {paper.category}</span>
<span className="text-[11px] text-gray-500">📅 {paper.date}</span>
</div>
{isOpen && (
<div className="mt-3 pt-3 border-t border-gray-700">
<p className="text-xs font-semibold text-green-400 mb-1">Abstract</p>
<p className="text-xs text-gray-300 leading-relaxed">{paper.abstract}</p>
</div>
)}
<p className="text-xs text-green-500 text-center mt-2">
{isOpen ? '▲ ' + t('research.less', 'Kêmtir / Less') : '▼ ' + t('research.more', 'Bêtir / More')}
</p>
</button>
);
})}
{/* Submit CTA */}
<div className="bg-green-900/20 border border-green-700/40 rounded-2xl p-4 text-center">
<p className="font-bold text-green-300 text-sm mb-1">
{t('research.submit.title', 'Lêkolîna xwe bişînin / Submit your research')}
</p>
<p className="text-xs text-gray-400 leading-relaxed mb-3">
{t('research.submit.desc', 'Hûn dikarin gotarên xwe yên zanistî ji bo vekolînê bişînin. Submit your academic papers for peer review.')}
</p>
<button className="bg-green-700 hover:bg-green-600 text-white text-sm font-semibold px-5 py-2 rounded-xl transition-colors">
📝 {t('research.submit.btn', 'Bişîne / Submit')}
</button>
</div>
</div>
<div className="h-10" />
</div>
);
}
+165
View File
@@ -0,0 +1,165 @@
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
interface Course {
id: string;
emoji: string;
titleKu: string;
title: string;
instructor: string;
duration: string;
level: 'Destpêk' | 'Navîn' | 'Pêşketî';
levelEn: 'Beginner' | 'Intermediate' | 'Advanced';
enrolled: number;
lessons: number;
description: string;
reward: string;
}
const COURSES: Course[] = [
{
id: '1', emoji: '🗣️',
titleKu: 'Zimanê Kurdî - Kurmancî', title: 'Kurdish Language - Kurmanji',
instructor: 'Prof. Berfîn Shêx', duration: '12 hefte / weeks',
level: 'Destpêk', levelEn: 'Beginner', enrolled: 456, lessons: 48,
description: 'Learn Kurmanji Kurdish from scratch. Covers alphabet, grammar, conversation, reading, and writing.',
reward: 'NFT Certificate + 50 HEZ',
},
{
id: '2', emoji: '🔗',
titleKu: 'Blockchain 101', title: 'Blockchain 101',
instructor: 'Dr. Azad Kurdo', duration: '8 hefte / weeks',
level: 'Destpêk', levelEn: 'Beginner', enrolled: 312, lessons: 32,
description: 'Introduction to blockchain technology. Learn about consensus, cryptography, smart contracts, and decentralized applications.',
reward: 'NFT Certificate + 30 HEZ',
},
{
id: '3', emoji: '💻',
titleKu: 'Pêşvebirina Smart Contract', title: 'Smart Contract Development',
instructor: 'Jîn Bakir', duration: '10 hefte / weeks',
level: 'Navîn', levelEn: 'Intermediate', enrolled: 187, lessons: 40,
description: 'Build smart contracts for the Pezkuwi network. Covers Solidity/Ink!, testing, deployment, and security best practices.',
reward: 'NFT Certificate + 100 HEZ',
},
{
id: '4', emoji: '📊',
titleKu: 'Aboriya Dijîtal û DeFi', title: 'Digital Economics & DeFi',
instructor: 'Prof. Serhat Demirtash', duration: '6 hefte / weeks',
level: 'Navîn', levelEn: 'Intermediate', enrolled: 234, lessons: 24,
description: 'Understand decentralized finance, tokenomics, yield farming, liquidity pools, and the Bereketli neighborhood economy model.',
reward: 'NFT Certificate + 40 HEZ',
},
{
id: '5', emoji: '🛡️',
titleKu: 'Ewlehiya Sîber û Blockchain', title: 'Cyber Security & Blockchain',
instructor: 'Kawa Zana', duration: '8 hefte / weeks',
level: 'Pêşketî', levelEn: 'Advanced', enrolled: 98, lessons: 32,
description: 'Advanced security topics including audit techniques, common vulnerabilities, formal verification, and incident response.',
reward: 'NFT Certificate + 150 HEZ',
},
{
id: '6', emoji: '🏛️',
titleKu: 'Dîroka Kurdistanê - Dijîtal', title: 'History of Kurdistan - Digital',
instructor: 'Prof. Dilovan Ehmed', duration: '10 hefte / weeks',
level: 'Destpêk', levelEn: 'Beginner', enrolled: 567, lessons: 40,
description: 'A comprehensive digital course on Kurdish history, culture, and the journey toward digital sovereignty and self-governance.',
reward: 'NFT Certificate + 25 HEZ',
},
];
const LEVEL_COLORS: Record<Course['level'], string> = {
'Destpêk': '#16a34a',
'Navîn': '#2563eb',
'Pêşketî': '#7c3aed',
};
export default function UniversityPage() {
const navigate = useNavigate();
const { t } = useTranslation();
const totalStudents = COURSES.reduce((s, c) => s + c.enrolled, 0);
return (
<div className="min-h-screen bg-gray-950 text-white">
{/* Header */}
<div className="bg-green-700 px-4 pt-4 pb-5">
<div className="flex items-center gap-3 mb-4">
<button onClick={() => navigate(-1)} className="text-white/80 hover:text-white text-xl leading-none"></button>
<span className="text-sm text-white/70">{t('university.breadcrumb', 'Education')}</span>
</div>
<div className="text-center">
<span className="text-5xl block mb-2">🎓</span>
<h1 className="text-2xl font-bold">{t('university.title', 'Zanîngeha Dijîtal')}</h1>
<p className="text-white/70 text-sm mt-0.5">{t('university.subtitle', 'Kurdistan Digital University')}</p>
</div>
<div className="flex justify-center gap-8 mt-4 bg-white/15 rounded-xl py-3 mx-4">
<div className="text-center">
<p className="text-xl font-bold">{COURSES.length}</p>
<p className="text-[10px] text-white/70">{t('university.stats.courses', 'Kurs / Courses')}</p>
</div>
<div className="w-px bg-white/30" />
<div className="text-center">
<p className="text-xl font-bold">{totalStudents.toLocaleString()}</p>
<p className="text-[10px] text-white/70">{t('university.stats.students', 'Xwendekar / Students')}</p>
</div>
</div>
</div>
<div className="px-4 py-4 space-y-3 max-w-lg mx-auto">
{COURSES.map(course => {
const color = LEVEL_COLORS[course.level];
return (
<div key={course.id} className="bg-gray-900 rounded-2xl p-4">
{/* Top row */}
<div className="flex items-center justify-between mb-3">
<span className="text-3xl">{course.emoji}</span>
<span
className="text-[11px] font-semibold px-2.5 py-1 rounded-full"
style={{ color, backgroundColor: color + '22' }}
>
{course.level} / {course.levelEn}
</span>
</div>
<p className="font-bold text-white text-sm">{course.titleKu}</p>
<p className="text-xs text-gray-400 mb-1">{course.title}</p>
<p className="text-xs text-blue-400 mb-3">👨🏫 {course.instructor}</p>
<p className="text-xs text-gray-300 leading-relaxed mb-3">{course.description}</p>
{/* Meta */}
<div className="flex justify-around bg-gray-800 rounded-xl py-2.5 mb-3">
<div className="text-center">
<p className="text-base">📚</p>
<p className="text-[11px] text-gray-400">{course.lessons} ders</p>
</div>
<div className="text-center">
<p className="text-base"></p>
<p className="text-[11px] text-gray-400">{course.duration}</p>
</div>
<div className="text-center">
<p className="text-base">👥</p>
<p className="text-[11px] text-gray-400">{course.enrolled}</p>
</div>
</div>
{/* Reward */}
<div className="flex items-center gap-2 mb-3">
<span className="text-xs text-gray-400">{t('university.reward', '🎁 Xelat / Reward:')}</span>
<span className="text-xs font-semibold text-yellow-400">{course.reward}</span>
</div>
{/* Enroll button */}
<button
className="w-full py-2.5 rounded-xl text-sm font-semibold text-white transition-opacity active:opacity-80"
style={{ backgroundColor: color }}
onClick={() => navigate('/education/certificates')}
>
{t('university.enroll', 'Tomar bibe / Enroll')}
</button>
</div>
);
})}
</div>
<div className="h-10" />
</div>
);
}