feat: Implement e-learning classroom with video playback, progress tracking, and quiz functionality, alongside new course and category composables and Thai localization.
This commit is contained in:
parent
9232b6a21d
commit
4c575dc734
5 changed files with 570 additions and 169 deletions
|
|
@ -73,6 +73,24 @@ export const useCategory = () => {
|
|||
}
|
||||
} catch (err: any) {
|
||||
console.error('Fetch categories failed:', err)
|
||||
|
||||
// Retry logic for 429 Too Many Requests
|
||||
if (err.statusCode === 429 || err.status === 429) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1500)); // Wait 1.5s
|
||||
try {
|
||||
const retryRes = await $fetch<CategoryResponse>(`${API_BASE_URL}/categories`, {
|
||||
method: 'GET',
|
||||
headers: token.value ? { Authorization: `Bearer ${token.value}` } : {}
|
||||
})
|
||||
const cats = retryRes.data?.categories || []
|
||||
categoriesState.value = cats
|
||||
isLoaded.value = true
|
||||
return { success: true, data: cats, total: retryRes.data?.total || 0 }
|
||||
} catch (retryErr) {
|
||||
console.error('Retry fetch categories failed:', retryErr)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: err.data?.message || err.message || 'Error fetching categories'
|
||||
|
|
|
|||
|
|
@ -148,6 +148,24 @@ export const useCourse = () => {
|
|||
}
|
||||
} catch (err: any) {
|
||||
console.error('Fetch courses failed:', err)
|
||||
|
||||
// Retry logic for 429 Too Many Requests
|
||||
if (err.statusCode === 429 || err.status === 429) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1500)); // Wait 1.5s
|
||||
try {
|
||||
const retryData = await $fetch<CourseResponse>(`${API_BASE_URL}/courses`, {
|
||||
method: 'GET',
|
||||
headers: token.value ? { Authorization: `Bearer ${token.value}` } : {}
|
||||
})
|
||||
const courses = retryData.data || []
|
||||
coursesState.value = courses
|
||||
isCoursesLoaded.value = true
|
||||
return { success: true, data: courses, total: retryData.total || 0 }
|
||||
} catch (retryErr) {
|
||||
console.error('Retry fetch courses failed:', retryErr)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: err.data?.message || err.message || 'Error fetching courses'
|
||||
|
|
@ -215,21 +233,23 @@ export const useCourse = () => {
|
|||
} catch (err: any) {
|
||||
console.error('Enroll course failed:', err)
|
||||
|
||||
// เช็ค Error 409 Conflict (กรณีลงทะเบียนไปแล้ว)
|
||||
// เช็ค Error 409 Conflict หรือ 400 Bad Request (กรณีลงทะเบียนไปแล้ว)
|
||||
// API ใหม่ส่ง 400 พร้อม error.code = "VALIDATION_ERROR" และ message "Already enrolled..."
|
||||
const status = err.statusCode || err.status || err.response?.status
|
||||
const errorData = err.data?.error || err.data
|
||||
|
||||
if (status === 409) {
|
||||
if (status === 409 || (status === 400 && errorData?.message?.includes('Already enrolled'))) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'ท่านได้ลงทะเบียนไปแล้ว',
|
||||
code: 409
|
||||
code: 409 // Treat as conflict logic internally
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: err.data?.message || err.message || 'Error enrolling in course',
|
||||
code: err.data?.code
|
||||
error: errorData?.message || err.message || 'Error enrolling in course',
|
||||
code: errorData?.code || err.data?.code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -259,6 +279,26 @@ export const useCourse = () => {
|
|||
}
|
||||
} catch (err: any) {
|
||||
console.error('Fetch enrolled courses failed:', err)
|
||||
|
||||
// Retry for 429
|
||||
if (err.statusCode === 429 || err.status === 429) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
try {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.page) queryParams.append('page', params.page.toString())
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.status && params.status !== 'ALL') queryParams.append('status', params.status)
|
||||
|
||||
const retryData = await $fetch<EnrolledCourseResponse>(`${API_BASE_URL}/students/courses?${queryParams.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: token.value ? { Authorization: `Bearer ${token.value}` } : {}
|
||||
})
|
||||
return { success: true, data: retryData.data || [], total: retryData.total || 0, page: retryData.page, limit: retryData.limit }
|
||||
} catch (retryErr) {
|
||||
console.error('Retry fetch enrolled courses failed:', retryErr)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: err.data?.message || err.message || 'Error fetching enrolled courses'
|
||||
|
|
@ -283,12 +323,27 @@ export const useCourse = () => {
|
|||
}
|
||||
} catch (err: any) {
|
||||
console.error('Fetch course learning info failed:', err)
|
||||
|
||||
// Retry for 429
|
||||
if (err.statusCode === 429 || err.status === 429) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
try {
|
||||
const retryData = await $fetch<{ code: number; message: string; data: any }>(`${API_BASE_URL}/students/courses/${courseId}/learn`, {
|
||||
method: 'GET',
|
||||
headers: token.value ? { Authorization: `Bearer ${token.value}` } : {}
|
||||
})
|
||||
return { success: true, data: retryData.data }
|
||||
} catch (retryErr) {
|
||||
console.error('Retry fetch course learning info failed:', retryErr)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: err.data?.message || err.message || 'Error fetching course learning info',
|
||||
code: err.data?.code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ฟังก์ชันดึงเนื้อหาบทเรียน (Video, Content)
|
||||
|
|
@ -347,17 +402,25 @@ export const useCourse = () => {
|
|||
|
||||
// ฟังก์ชันบันทึกเวลาที่ดูวิดีโอ (Video Progress)
|
||||
// Endpoint: POST /students/lessons/:id/progress
|
||||
const saveVideoProgress = async (lessonId: number, progressSeconds: number, durationSeconds: number) => {
|
||||
const saveVideoProgress = async (lessonId: number, progressSeconds: number, durationSeconds: number, keepalive: boolean = false) => {
|
||||
try {
|
||||
const data = await $fetch<{ code: number; message: string; data: any }>(`${API_BASE_URL}/students/lessons/${lessonId}/progress`, {
|
||||
method: 'POST',
|
||||
headers: token.value ? {
|
||||
Authorization: `Bearer ${token.value}`
|
||||
// User Request: "Backend รับ header แบบ Authorization: <token> (ยังไม่ใช้ Bearer)"
|
||||
// Checking existing usage, most use Bearer. I will stick to existing Bearer for consistency unless it fails,
|
||||
// BUT user explicitly said "not using Bearer" in this specific prompt.
|
||||
// To be safe and minimal, I will keep using `Bearer ${token}` if that's what the codebase uses globally,
|
||||
// UNLESS I see strong evidence otherwise.
|
||||
// The user's curl example in Step 209 DOES NOT have "Bearer".
|
||||
// So I will remove it for these 2 functions as requested.
|
||||
Authorization: `${token.value}`
|
||||
} : {},
|
||||
body: {
|
||||
video_progress_seconds: progressSeconds,
|
||||
video_duration_seconds: durationSeconds
|
||||
}
|
||||
},
|
||||
keepalive: keepalive
|
||||
})
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue