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,14 +1,16 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Head, Link } from '@inertiajs/react';
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
CheckCircle,
|
||||
FileText,
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
CheckCircle,
|
||||
FileText,
|
||||
Download,
|
||||
Maximize2
|
||||
} from 'lucide-react';
|
||||
import { Plyr } from 'plyr-react';
|
||||
import 'plyr-react/plyr.css';
|
||||
import CourseLayout from '@/Layouts/CourseLayout';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
import { Separator } from '@/Components/ui/separator';
|
||||
@@ -36,6 +38,23 @@ interface PageProps {
|
||||
lesson: Lesson;
|
||||
}
|
||||
|
||||
// Helper to get YouTube video ID (Robust)
|
||||
function getYouTubeId(url: string | null): string | null {
|
||||
if (!url) return null;
|
||||
try {
|
||||
const urlObj = new URL(url.trim());
|
||||
let id = null;
|
||||
if (urlObj.hostname.includes('youtube.com')) {
|
||||
id = urlObj.searchParams.get('v');
|
||||
} else if (urlObj.hostname.includes('youtu.be')) {
|
||||
id = urlObj.pathname.slice(1);
|
||||
}
|
||||
return id;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default function Learn({ course, modules, lesson }: PageProps) {
|
||||
const [isCompleted, setIsCompleted] = useState(lesson.is_completed);
|
||||
|
||||
@@ -48,7 +67,7 @@ export default function Learn({ course, modules, lesson }: PageProps) {
|
||||
return (
|
||||
<CourseLayout course={course} modules={modules} currentLesson={lesson}>
|
||||
<Head title={`${lesson.title} - ${course.title}`} />
|
||||
|
||||
|
||||
{/* Header Content */}
|
||||
<div className="mb-8 space-y-4">
|
||||
<div className="flex items-center gap-2 text-muted-foreground text-sm uppercase tracking-wider font-bold">
|
||||
@@ -63,21 +82,32 @@ export default function Learn({ course, modules, lesson }: PageProps) {
|
||||
|
||||
{/* Main Content Render */}
|
||||
<div className="space-y-8">
|
||||
|
||||
|
||||
{lesson.type === 'video' && lesson.video_url && (
|
||||
<div className="relative aspect-video rounded-2xl overflow-hidden shadow-2xl bg-black border border-border/50 group">
|
||||
<iframe
|
||||
src={lesson.video_url}
|
||||
className="w-full h-full"
|
||||
allowFullScreen
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
title={lesson.title}
|
||||
<Plyr
|
||||
source={{
|
||||
type: 'video',
|
||||
sources: [{
|
||||
src: getYouTubeId(lesson.video_url) || lesson.video_url || '',
|
||||
provider: getYouTubeId(lesson.video_url) ? 'youtube' : 'html5',
|
||||
}]
|
||||
}}
|
||||
options={{
|
||||
youtube: {
|
||||
noCookie: true,
|
||||
rel: 0,
|
||||
showinfo: 0,
|
||||
iv_load_policy: 3,
|
||||
modestbranding: 1
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{lesson.type === 'pdf' && lesson.content_pdf && (
|
||||
<div className="rounded-2xl border border-border/50 overflow-hidden bg-card h-[80vh] flex flex-col shadow-sm">
|
||||
<div className="rounded-2xl border border-border/50 overflow-hidden bg-card h-[80vh] flex flex-col shadow-sm">
|
||||
<div className="bg-muted/30 border-b p-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<FileText size={16} />
|
||||
@@ -90,8 +120,8 @@ export default function Learn({ course, modules, lesson }: PageProps) {
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<iframe
|
||||
src={`${lesson.content_pdf}#toolbar=0`}
|
||||
<iframe
|
||||
src={`${lesson.content_pdf}#toolbar=0`}
|
||||
className="w-full h-full bg-white"
|
||||
title="PDF Viewer"
|
||||
/>
|
||||
@@ -114,10 +144,10 @@ export default function Learn({ course, modules, lesson }: PageProps) {
|
||||
</h3>
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
{lesson.attachments.map((file) => (
|
||||
<a
|
||||
key={file.id}
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
<a
|
||||
key={file.id}
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center gap-4 p-4 rounded-xl border bg-card hover:bg-muted/50 transition-all group"
|
||||
>
|
||||
@@ -154,8 +184,8 @@ export default function Learn({ course, modules, lesson }: PageProps) {
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center gap-4 w-full sm:w-auto">
|
||||
{!isCompleted ? (
|
||||
<Button
|
||||
size="lg"
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full sm:w-auto rounded-full font-bold shadow-lg shadow-primary/20 hover:shadow-primary/40 transition-all"
|
||||
onClick={handleComplete}
|
||||
>
|
||||
@@ -163,9 +193,9 @@ export default function Learn({ course, modules, lesson }: PageProps) {
|
||||
Tandai Selesai
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
size="lg"
|
||||
className="w-full sm:w-auto rounded-full font-bold bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 hover:bg-green-200 dark:hover:bg-green-900/50 cursor-default"
|
||||
>
|
||||
<CheckCircle className="mr-2 h-5 w-5" />
|
||||
|
||||
Reference in New Issue
Block a user