DEV Community

Cover image for AI Flashcard: NextJs (Basic)
Taki
Taki

Posted on

AI Flashcard: NextJs (Basic)

1. Create a FlashCard Component

Create a file called components/FlashCard.tsx:

import React from 'react';

export interface FlashCard {
  phrase: string;
  meaning: string;
  breakdown: string[];
  simpleMeaning: string;
  usageContext: string;
  examples: {
    sentence: string;
    explanation: string;
  }[];
  similarPhrases: string[];
  tip: string;
  domainUsage: string;
}

interface FlashCardProps {
  card: FlashCard;
}

const FlashCard: React.FC<FlashCardProps> = ({ card }) => {
  return (
    <div className="max-w-3xl mx-auto bg-white shadow-lg rounded-lg p-6 my-6">
      {/* Phrase Header */}
      <h2 className="text-2xl font-bold mb-4">πŸ”€ {card.phrase}</h2>

      {/* Explanation Section */}
      <section className="mb-4">
        <h3 className="text-xl font-semibold mb-2">πŸ“˜ Explanation</h3>
        <p className="mb-2"><strong>Definition:</strong> {card.meaning}</p>
        <p className="mb-2">
          <strong>Phrase Breakdown:</strong>
          <ul className="list-disc list-inside">
            {card.breakdown.map((item, index) => (
              <li key={index}>{item}</li>
            ))}
          </ul>
        </p>
        <p className="mb-2"><strong>Meaning (in simple English):</strong> {card.simpleMeaning}</p>
        <p className="mb-2"><strong>Usage Context:</strong> {card.usageContext}</p>
      </section>

      {/* Examples Section */}
      {card.examples.length > 0 && (
        <section className="mb-4">
          <h3 className="text-xl font-semibold mb-2">🧠 Examples</h3>
          <table className="table-auto border-collapse w-full">
            <thead>
              <tr>
                <th className="border p-2 text-left">English Sentence</th>
                <th className="border p-2 text-left">Simple Meaning</th>
              </tr>
            </thead>
            <tbody>
              {card.examples.map((ex, index) => (
                <tr key={index}>
                  <td className="border p-2">{ex.sentence}</td>
                  <td className="border p-2">{ex.explanation}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </section>
      )}

      {/* Similar Phrases */}
      {card.similarPhrases.length > 0 && (
        <section className="mb-4">
          <h3 className="text-xl font-semibold mb-2">πŸ” Similar Phrases</h3>
          <ul className="list-disc list-inside">
            {card.similarPhrases.map((phrase, index) => (
              <li key={index}>{phrase}</li>
            ))}
          </ul>
        </section>
      )}

      {/* Memory Tip */}
      {card.tip && (
        <section className="mb-4">
          <h3 className="text-xl font-semibold mb-2">πŸ“Œ Memory Tip</h3>
          <p>{card.tip}</p>
        </section>
      )}

      {/* Field / Domain Usage */}
      {card.domainUsage && (
        <section>
          <h3 className="text-xl font-semibold mb-2">🧩 Field Usage</h3>
          <p>{card.domainUsage}</p>
        </section>
      )}
    </div>
  );
};

export default FlashCard;
Enter fullscreen mode Exit fullscreen mode

2. Create a Next.js Page to Consume the API

Here’s an example of a simple page in the app directory (if you’re using the Next.js App Router) or pages/index.tsx if you’re on the Pages Router.

If using App Router (Next.js 13+ with /app):

Create or update app/page.tsx:

'use client';

import { useState } from 'react';
import FlashCard, { FlashCard as FlashCardType } from '../components/FlashCard';

export default function Home() {
  const [input, setInput] = useState('');
  const [flashCard, setFlashCard] = useState<FlashCardType | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string>('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError('');
    setFlashCard(null);

    try {
      const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/phrase/explain`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ text: input }),
      });
      if (!res.ok) {
        throw new Error(`Error: ${res.statusText}`);
      }
      const data: FlashCardType = await res.json();
      setFlashCard(data);
    } catch (err: any) {
      console.error(err);
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <main className="max-w-4xl mx-auto p-8">
      <h1 className="text-4xl font-bold mb-6 text-center">🧠 AI Dictionary Flashcard</h1>
      <form onSubmit={handleSubmit} className="flex gap-4 mb-8">
        <input
          className="flex-1 border rounded-md p-3"
          type="text"
          placeholder="Enter an English phrase..."
          value={input}
          onChange={(e) => setInput(e.target.value)}
        />
        <button
          className="bg-blue-600 text-white px-6 py-3 rounded-md hover:bg-blue-700"
          type="submit"
          disabled={loading || !input.trim()}
        >
          {loading ? 'Loading...' : 'Generate Flashcard'}
        </button>
      </form>

      {error && <p className="text-red-600">{error}</p>}

      {flashCard && <FlashCard card={flashCard} />}
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

If using Pages Router (create pages/index.tsx):

import { useState } from 'react';
import FlashCard, { FlashCard as FlashCardType } from '../components/FlashCard';

export default function Home() {
  const [input, setInput] = useState('');
  const [flashCard, setFlashCard] = useState<FlashCardType | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string>('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError('');
    setFlashCard(null);

    try {
      const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/phrase/explain`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ text: input }),
      });
      if (!res.ok) {
        throw new Error(`Error: ${res.statusText}`);
      }
      const data: FlashCardType = await res.json();
      setFlashCard(data);
    } catch (err: any) {
      console.error(err);
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="max-w-4xl mx-auto p-8">
      <h1 className="text-4xl font-bold mb-6 text-center">🧠 AI Dictionary Flashcard</h1>
      <form onSubmit={handleSubmit} className="flex gap-4 mb-8">
        <input
          className="flex-1 border rounded-md p-3"
          type="text"
          placeholder="Enter an English phrase..."
          value={input}
          onChange={(e) => setInput(e.target.value)}
        />
        <button
          className="bg-blue-600 text-white px-6 py-3 rounded-md hover:bg-blue-700"
          type="submit"
          disabled={loading || !input.trim()}
        >
          {loading ? 'Loading...' : 'Generate Flashcard'}
        </button>
      </form>

      {error && <p className="text-red-600">{error}</p>}

      {flashCard && <FlashCard card={flashCard} />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Environment Variables

Ensure you have a .env.local file in your Next.js project with:

NEXT_PUBLIC_API_URL=http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Replace http://localhost:3000 with your backend URL as needed.


βœ… 4. Enable CORS in NestJS (for local testing)

In main.ts (NestJS):

app.enableCors({
  origin: 'http://localhost:3000',
});
Enter fullscreen mode Exit fullscreen mode

βœ… 5. Run Both Apps

# Backend
npm run start:dev  # (port 3000)

# Frontend
npm run dev        # (port 3000 default β†’ change one to 3001)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)