diff --git a/src/app/decks/[deck_id]/learn/Memorize.tsx b/src/app/decks/[deck_id]/learn/Memorize.tsx
index 9f5f65f..bca966b 100644
--- a/src/app/decks/[deck_id]/learn/Memorize.tsx
+++ b/src/app/decks/[deck_id]/learn/Memorize.tsx
@@ -8,7 +8,11 @@ import { Layers, Check, Clock, Sparkles, RotateCcw, Volume2, Headphones } from "
import type { ActionOutputCardWithNote, ActionOutputScheduledCard } from "@/modules/card/card-action-dto";
import { actionGetCardsForReview, actionAnswerCard } from "@/modules/card/card-action";
import { PageLayout } from "@/components/ui/PageLayout";
-import { LightButton } from "@/design-system/base/button";
+import { LightButton, CircleButton } from "@/design-system/base/button";
+import { Badge } from "@/design-system/data-display/badge";
+import { Progress } from "@/design-system/feedback/progress";
+import { Skeleton } from "@/design-system/feedback/skeleton";
+import { HStack, VStack } from "@/design-system/layout/stack";
import { CardType } from "../../../../../generated/prisma/enums";
import { calculatePreviewIntervals, formatPreviewInterval, type CardPreview } from "./interval-preview";
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
@@ -38,8 +42,6 @@ const Memorize: React.FC
= ({ deckId, deckName }) => {
const [error, setError] = useState(null);
const [isReversed, setIsReversed] = useState(false);
const [isDictation, setIsDictation] = useState(false);
- const [dictationInput, setDictationInput] = useState("");
- const [dictationResult, setDictationResult] = useState<"correct" | "incorrect" | null>(null);
const { play, stop, load } = useAudioPlayer();
const audioUrlRef = useRef(null);
const [isAudioLoading, setIsAudioLoading] = useState(false);
@@ -60,8 +62,6 @@ const Memorize: React.FC = ({ deckId, deckName }) => {
setLastScheduled(null);
setIsReversed(false);
setIsDictation(false);
- setDictationInput("");
- setDictationResult(null);
} else {
setError(result.message);
}
@@ -87,18 +87,7 @@ const Memorize: React.FC = ({ deckId, deckName }) => {
const handleShowAnswer = useCallback(() => {
setShowAnswer(true);
-
- if (isDictation) {
- const currentCard = getCurrentCard();
- if (currentCard) {
- const fields = getNoteFields(currentCard);
- const answer = isReversed ? (fields[0] ?? "") : (fields[1] ?? "");
- const normalizedInput = dictationInput.trim().toLowerCase();
- const normalizedAnswer = answer.trim().toLowerCase();
- setDictationResult(normalizedInput === normalizedAnswer ? "correct" : "incorrect");
- }
- }
- }, [isDictation, dictationInput, isReversed]);
+ }, []);
const handleAnswer = useCallback((ease: ReviewEase) => {
const card = getCurrentCard();
@@ -125,8 +114,6 @@ const Memorize: React.FC = ({ deckId, deckName }) => {
setShowAnswer(false);
setIsReversed(false);
setIsDictation(false);
- setDictationInput("");
- setDictationResult(null);
if (audioUrlRef.current) {
URL.revokeObjectURL(audioUrlRef.current);
@@ -251,28 +238,28 @@ const Memorize: React.FC = ({ deckId, deckName }) => {
}
};
- const getCardTypeColor = (type: CardType): string => {
+ const getCardTypeVariant = (type: CardType): "info" | "warning" | "success" | "primary" => {
switch (type) {
case CardType.NEW:
- return "bg-blue-100 text-blue-700";
+ return "info";
case CardType.LEARNING:
- return "bg-yellow-100 text-yellow-700";
+ return "warning";
case CardType.REVIEW:
- return "bg-green-100 text-green-700";
+ return "success";
case CardType.RELEARNING:
- return "bg-purple-100 text-purple-700";
+ return "primary";
default:
- return "bg-gray-100 text-gray-700";
+ return "info";
}
};
if (isLoading) {
return (
-
+
);
}
@@ -280,12 +267,14 @@ const Memorize: React.FC = ({ deckId, deckName }) => {
if (error) {
return (
-
-
{error}
+
+
+ {error}
+
router.push("/decks")} className="px-4 py-2">
{t("backToDecks")}
-
+
);
}
@@ -293,7 +282,7 @@ const Memorize: React.FC = ({ deckId, deckName }) => {
if (cards.length === 0) {
return (
-
+
@@ -302,7 +291,7 @@ const Memorize: React.FC = ({ deckId, deckName }) => {
router.push("/decks")} className="px-4 py-2">
{t("backToDecks")}
-
+
);
}
@@ -325,162 +314,118 @@ const Memorize: React.FC = ({ deckId, deckName }) => {
return (
-
-
+
+
{deckName}
-
-
-
+
+
+
{getCardTypeLabel(currentCard.type)}
-
+
{t("progress", { current: currentIndex + 1, total: cards.length + currentIndex })}
-
-
+
+
-
+
{lastScheduled && (
-
+
{t("nextReview")}: {formatNextReview(lastScheduled)}
-
+
)}
-
-
-
-
+
+
{isDictation ? (
<>
-
-
+
{t("clickToPlay")}
-
-
- {showAnswer ? (
- <>
-
-
-
-
- setDictationInput(e.target.value)}
- className={`w-full p-3 border rounded-lg text-lg ${
- dictationResult === "correct"
- ? "border-green-500 bg-green-50"
- : dictationResult === "incorrect"
- ? "border-red-500 bg-red-50"
- : "border-gray-300"
- }`}
- readOnly
- />
-
-
-
- {dictationResult === "correct" ? "✓ " + t("correct") : "✗ " + t("incorrect")}
-
- {dictationResult === "incorrect" && (
-
{displayBack}
- )}
-
-
- >
- ) : (
-
- setDictationInput(e.target.value)}
- placeholder={t("typeWhatYouHear")}
- className="w-full p-3 border border-gray-300 rounded-lg text-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
- autoFocus
- />
-
- )}
- >
- ) : (
- <>
-
+
{showAnswer && (
<>
-
+
+ >
+ )}
+ >
+ ) : (
+ <>
+
+
+ {displayFront}
+
+
+
+ {showAnswer && (
+ <>
+
+
+
+ {displayBack}
+
+
>
)}
>
)}
-
+
{t("interval")}: {formatInterval(currentCard.ivl)}
•
{t("ease")}: {currentCard.factor / 10}%
•
{t("lapses")}: {currentCard.lapses}
-
+
-
+
{!showAnswer ? (
= ({ deckId, deckName }) => {
Space
) : (
-
+
{formatPreviewInterval(previewIntervals.good)}
- 3/Space
-
+
)}
-
+
);
};
diff --git a/src/app/globals.css b/src/app/globals.css
index e60a85e..c5c009a 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -177,7 +177,7 @@ body {
height: 100%;
margin: 0;
padding: 0;
- background: var(--background);
+ background: var(--primary-50);
color: var(--foreground);
font-family: var(--font-geist-sans), -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
font-size: 1rem;