mirror of
https://github.com/nihonbuzz/nihonbuzz-academy.git
synced 2026-01-26 13:32:07 +07:00
feat: Implement a new course learning system with dedicated layouts, lesson playback, and Spaced Repetition System (SRS) functionality.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
|
||||
import DashboardLayout from '@/Layouts/DashboardLayout';
|
||||
import { Head, router } from '@inertiajs/react';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
import { Card } from '@/Components/ui/card';
|
||||
@@ -32,7 +32,7 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
if (!isFlipped && !isFinished) {
|
||||
setIsFlipped(true);
|
||||
if (currentItem?.audio_url) {
|
||||
new Audio(currentItem.audio_url).play().catch(() => {});
|
||||
new Audio(currentItem.audio_url).play().catch(() => { });
|
||||
}
|
||||
}
|
||||
}, [isFlipped, isFinished, currentItem]);
|
||||
@@ -42,7 +42,7 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
|
||||
// Optimistic UI update
|
||||
const itemToSubmit = currentItem;
|
||||
|
||||
|
||||
// Submit in background
|
||||
router.post(route('srs.store'), {
|
||||
vocabulary_id: itemToSubmit.id,
|
||||
@@ -68,7 +68,7 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (isFinished) return;
|
||||
|
||||
|
||||
if (e.code === 'Space') {
|
||||
e.preventDefault();
|
||||
if (!isFlipped) handleFlip();
|
||||
@@ -85,7 +85,7 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
}, [handleFlip, handleGrade, isFinished, isFlipped]);
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout header={
|
||||
<DashboardLayout header={
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex flex-col">
|
||||
<h2 className="text-2xl font-black tracking-tight text-foreground">
|
||||
@@ -119,9 +119,9 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
</div>
|
||||
<h3 className="text-3xl font-black text-foreground mb-3">Otsukaresama! 🎉</h3>
|
||||
<p className="text-muted-foreground font-medium mb-10">Sesi ulasan Anda telah selesai dengan luar biasa.</p>
|
||||
<Button
|
||||
onClick={() => router.visit(route('srs.index'))}
|
||||
size="lg"
|
||||
<Button
|
||||
onClick={() => router.visit(route('srs.index'))}
|
||||
size="lg"
|
||||
className="w-full rounded-[1.5rem] h-14 text-lg font-bold bg-primary hover:bg-primary/90 shadow-xl shadow-primary/20 hover:scale-105 active:scale-95 transition-all"
|
||||
>
|
||||
Kembali ke Dashboard
|
||||
@@ -131,7 +131,7 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
) : (
|
||||
<div className="w-full max-w-2xl px-4 flex flex-col items-center">
|
||||
{/* Flashcard Area */}
|
||||
<div
|
||||
<div
|
||||
className="w-full aspect-[16/10] sm:aspect-[16/9] perspective-2000 cursor-pointer group mb-12"
|
||||
onClick={handleFlip}
|
||||
>
|
||||
@@ -147,7 +147,7 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
{currentItem.type === 'new' ? 'New Discovery' : 'Active Review'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<h1 className="text-7xl md:text-9xl font-black text-foreground tracking-tighter drop-shadow-sm">
|
||||
{currentItem.word}
|
||||
@@ -162,14 +162,14 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
{/* Back Side */}
|
||||
<Card className="absolute w-full h-full backface-hidden rotate-y-180 flex flex-col items-center justify-center p-12 bg-card/60 backdrop-blur-3xl border border-primary/20 rounded-[3rem] shadow-2xl">
|
||||
<div className="absolute top-8 left-1/2 -translate-x-1/2 border-b-4 border-primary/30 w-12 rounded-full" />
|
||||
|
||||
|
||||
<div className="flex flex-col items-center text-center space-y-6 w-full">
|
||||
<h2 className="text-5xl md:text-6xl font-black text-primary tracking-tight">
|
||||
{currentItem.reading}
|
||||
</h2>
|
||||
|
||||
|
||||
<div className="h-px w-24 bg-border/50" />
|
||||
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-3xl md:text-4xl text-foreground font-black tracking-tight">
|
||||
{currentItem.meaning}
|
||||
@@ -178,13 +178,13 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
</div>
|
||||
|
||||
{currentItem.audio_url && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
new Audio(currentItem.audio_url!).play();
|
||||
}}
|
||||
}}
|
||||
className="mt-6 rounded-2xl h-14 w-14 p-0 bg-primary/10 text-primary hover:bg-primary hover:text-white transition-all shadow-lg shadow-primary/5"
|
||||
>
|
||||
<Volume2 size={24} />
|
||||
@@ -206,9 +206,9 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
{ val: 3, label: 'Good', desc: 'Bagus', color: 'blue' },
|
||||
{ val: 4, label: 'Easy', desc: 'Mudah', color: 'green' }
|
||||
].map((btn) => (
|
||||
<Button
|
||||
<Button
|
||||
key={btn.val}
|
||||
variant="outline"
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"flex-1 min-w-[120px] h-20 flex flex-col gap-1 rounded-2xl border-2 transition-all hover:-translate-y-1",
|
||||
btn.color === 'red' && "border-red-500/20 bg-red-500/5 hover:bg-red-500/10 text-red-600 hover:border-red-500/40",
|
||||
@@ -223,7 +223,7 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Keyboard Tips */}
|
||||
<div className={cn(
|
||||
"mt-8 text-center transition-opacity duration-1000",
|
||||
@@ -237,6 +237,6 @@ export default function SrsPractice({ items }: { items: ReviewItem[] }) {
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</AuthenticatedLayout>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user