بناء تطبيق بطاقات تعليمية (Flashcards) باستخدام Next.js و MongoDB

بناء تطبيق بطاقات تعليمية (Flashcards) باستخدام Next.js و MongoDB

بناء تطبيق بطاقات تعليمية (Flashcards) باستخدام Next.js و MongoDB

لو مررت بتجربة المذاكرة ليلة الامتحان، فأنت تعرف كم صعب تتذكر كل حاجة. البطاقات التعليمية (Flashcards) من أنفع أدوات المذاكرة لأنها بتستخدم "الاستدعاء النشط" (active recall) – بتخلي دماغك يسترجع المعلومة بدل ما تقرها بس بشكل سلبي.

بناء تطبيق بطاقات تعليمية رقمي بيمزج بين تطوير الويب الحديث وعلم النفس التربوي. في الدليل ده، هوريك خطوة بخطوة إزاي تبني تطبيق full-stack للبطاقات التعليمية باستخدام Next.js للفرونت إند والـ API routes، و MongoDB لتخزين البيانات.

ليه نبني تطبيق بطاقات تعليمية؟

البطاقات الرقمية ليها مميزات كتير عن الورقية:

- **التنقل**: توصل لـ decks بتاعك من أي مكان فيه نت
- **التكرار المتباعد**: خوارزميات بتحسن توقيت المراجعة
- **دعم الوسائط**: صور، صوت، وتنسيق
- **تتبع التقدم**: تحليلات لأداء تعلمك

حالات استخدام واقعية

قبل ما ندخل في الكود، خلينا نفكر في سيناريوهات تطبيقنا هينفع فيها:

1. **طلاب الطب**: إنشاء بطاقات تشريح مفصلة بصور
2. **متعلمي اللغات**: بناء مجموعات مفردات مع نطق صوتي
3. **الشهادات المهنية**: تنظيم مفاهيم تقنية معقدة بشكل منهجي

نظرة عامة على التقنيات المستخدمة

هنستخدم:
- **Next.js 14** (إطار عمل React مع server-side rendering)
- **MongoDB Atlas** (قاعدة بيانات سحابية)
- **Mongoose** (نمذجة الكائنات لـ MongoDB)
- **Tailwind CSS** (للستايلنج)

ليه الـ stack ده؟ Next.js بيتعامل مع الفرونت إند والـ API routes، وده بيسهل الـ deployment. Schema المرنة في MongoDB بتنفع جداً مع هياكل البطاقات المتغيرة.

إعداد المشروع

أول حاجة، ننشئ مشروع Next.js جديد:

```bash
npx create-next-app@latest flashcard-app
cd flashcard-app
npm install mongoose mongodb
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

نضبط Tailwind في `tailwind.config.js`:

```javascript
/** @type {import('tailwindcss').tailwind.config} */
module.exports = {
content: [
"./app/**/*.{js,jsx,ts,jsx}",
"./pages/**/*.{js,jsx,ts,jsx}",
"./components/**/*.{js,jsx,ts,jsx}",
],
theme: {
extend: {},
},
plugins: [],
}
```

نضيف لـ `app/globals.css`:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

الاتصال بقاعدة البيانات

ننشئ `lib/mongodb.js`:

```javascript
import { MongoClient } from 'mongodb'

const uri = process.env.MONGODB_URI
const options = {
useNewUrlParser: true,
useUnifiedTopology: true,
}

let client
let clientPromise

if (!process.env.MONGODB_URI) {
throw new Error('Please add your MongoDB URI to .env.local')
}

if (process.env.NODE_ENV === 'development') {
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
}
clientPromise = global._mongoClientPromise
} else {
client = new MongoClient(uri, options)
clientPromise = client.connect()
}

export default clientPromise
```

إنشاء نموذج البطاقة التعليمية

في `models/Flashcard.js`:

```javascript
import mongoose from 'mongoose'

const flashcardSchema = new mongoose.Schema({
question: {
type: String,
required: true,
},
answer: {
type: String,
required: true,
},
deck: {
type: String,
required: true,
},
tags: [String],
createdAt: {
type: Date,
default: Date.now,
},
})

export default mongoose.models.Flashcard || mongoose.model('Flashcard', flashcardSchema)
```

مسارات الـ API

مسارات الـ API في Next.js بتتعامل مع منطق الباك إند. ننشئ `pages/api/flashcards/index.js`:

```javascript
import dbConnect from '../../../lib/dbConnect'
import Flashcard from '../../../models/Flashcard'

export default async function handler(req, res) {
const { method } = req

await dbConnect()

switch (method) {
case 'GET':
try {
const flashcards = await Flashcard.find({})
res.status(200).json(flashcards)
} catch (error) {
res.status(400).json({ success: false })
}
break
case 'POST':
try {
const flashcard = await Flashcard.create(req.body)
res.status(201).json(flashcard)
} catch (error) {
res.status(400).json({ success: false })
}
break
default:
res.status(400).json({ success: false })
break
}
}
```

مكونات الواجهة الأمامية

ننشئ مكون الفورم `components/FlashcardForm.js`:

```javascript
import { useState } from 'react'

export default function FlashcardForm({ onSubmit }) {
const [question, setQuestion] = useState('')
const [answer, setAnswer] = useState('')
const [deck, setDeck] = useState('')

const handleSubmit = async (e) => {
e.preventDefault()
const response = await fetch('/api/flashcards', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, answer, deck })
})

if (response.ok) {
const newFlashcard = await response.json()
onSubmit(newFlashcard)
setQuestion('')
setAnswer('')
setDeck('')
}
}

return (



value={question}
onChange={(e) => setQuestion(e.target.value)}
required
className="w-full p-2 border rounded"
/>



value={answer}
onChange={(e) => setAnswer(e.target.value)}
required
className="w-full p-2 border rounded"
/>



type="text"
value={deck}
onChange={(e) => setDeck(e.target.value)}
required
className="w-full p-2 border rounded"
/>



)
}
```

واجهة المذاكرة

ننشئ `components/StudyMode.js` لممارسة الاستدعاء النشط:

```javascript
import { useState, useEffect } from 'react'

export default function StudyMode({ flashcards }) {
const [currentIndex, setCurrentIndex] = useState(0)
const [showAnswer, setShowAnswer] = useState(false)
const [shuffledCards, setShuffledCards] = useState([])

useEffect(() => {
setShuffledCards([...flashcards].sort(() => Math.random() - 0.5))
}, [flashcards])

const nextCard = () => {
setShowAnswer(false)
if (currentIndex < shuffledCards.length - 1) {
setCurrentIndex(currentIndex + 1)
}
}

const prevCard = () => {
setShowAnswer(false)
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1)
}
}

if (!shuffledCards.length) return

لا توجد بطاقات متاحة



const card = shuffledCards[currentIndex]

return (


السؤال:


{card.question}




{showAnswer && (

الإجابة:


{card.answer}



)}





{currentIndex > 0 && (

)}
{currentIndex < shuffledCards.length - 1 && (

)}




بطاقة {currentIndex + 1} من {shuffledCards.length}



)
}
```

الصفحة الرئيسية للتطبيق

ننشئ `app/page.js`:

```javascript
import { useState, useEffect } from 'react'
import FlashcardForm from '../components/FlashcardForm'
import StudyMode from '../components/StudyMode'

export default function Home() {
const [flashcards, setFlashcards] = useState([])
const [activeView, setActiveView] = useState('create')
const [selectedDeck, setSelectedDeck] = useState('')

useEffect(() => {
fetchFlashcards()
}, [])

const fetchFlashcards = async () => {
const response = await fetch('/api/flashcards')
const data = await response.json()
setFlashcards(data)
}

const addFlashcard = (newFlashcard) => {
setFlashcards([newFlashcard, ...flashcards])
}

const filteredCards = selectedDeck
? flashcards.filter(card => card.deck === selectedDeck)
: flashcards

const decks = [...new Set(flashcards.map(card => card.deck))]

return (


تطبيق البطاقات التعليمية




onClick={() => setActiveView('create')}
className={`px-4 py-2 mr-2 rounded ${
activeView === 'create' ? 'bg-blue-500 text-white' : 'bg-white'
}`}
>
إنشاء بطاقات

onClick={() => setActiveView('study')}
className={`px-4 py-2 rounded ${
activeView === 'study' ? 'bg-blue-500 text-white' : 'bg-white'
}`}
disabled={flashcards.length === 0}
>
وضع المذاكرة



{activeView === 'create' && (



)}

{activeView === 'study' && (


value={selectedDeck}
onChange={(e) => setSelectedDeck(e.target.value)}
className="w-full p-2 border rounded"
>

{decks.map(deck => (

))}


)}

{activeView === 'study' && }


مجموعاتك


{decks.length === 0 ? (

مفيش مجموعات لسه. أنشئ أول بطاقة فوق!


) : (

    {decks.map(deck => (

  • {deck} ({flashcards.filter(c => c.deck === deck).length} بطاقة)

  • ))}

)}



)
}
```

اعتبارات النشر

للإنتاج، فكّر في التحسينات دي:

1. **المصادقة**: أضف حسابات مستخدمين بـ NextAuth.js
2. **التكرار المتباعد**: طبق خوارزميات زي SM-2
3. **الوسائط الغنية**: دعم الصور والصوت في البطاقات
4. **دعم الأوفلاين**: استخدم service workers لقدرات PWA

انشر على Vercel (مُحسّن لـ Next.js) أو منصة مشابهة. اعمل cluster على MongoDB Atlas وضبط متغيرات البيئة.

خاتمة

تطبيق البطاقات ده بيوضح المفاهيم الأساسية مع بقاءه عملي. عندك الآن أساس ممكن تبني عليه حسب احتياجاتك. سواء بتدرس لغات، بتستعد لامتحانات، أو بتحفظ مواد مهنية، البطاقات الرقمية بتوفر حل فعال وقابل للتخصيص.

ابدأ ببساطة، حسّن باستمرار، وركز على تجربة المستخدم الأساسية. بالتوفيق في رحلة تعلمك!

الأسئلة الشائعة

س: محتاج خبرة سابقة مع MongoDB؟
ج: المعرفة الأساسية بتساعد لكن مش مطلوبة. نموذج المستندات في MongoDB بديهي بمجرد ما تفهم تراكيب شبيهة بـ JSON.

س: أقدر أضيف صور للبطاقات؟
ج: أيوه! مدد الـ schema بروابط صور وعدّل مكونات الفورم تتعامل مع رفع الملفات.

س: إزاي أطبق التكرار المتباعد؟
ج: خزن سجل المراجعة بالطوابع الزمنية، ثم احسب الفواصل المثلى بناءً على أداء الاستدعاء. مكتبات زي `supermemo` ممكن تساعد في الخوارزميات.

س: التطبيق ده مناسب للموبايل؟
ج: مع أطر CSS متجاوبة زي Tailwind، أيوه. فكّر تضيف ميزات PWA لتجربة شبه أصلية.

Comments (0)

No comments yet. Be the first to comment!

Leave a Comment