feat: Add core e-learning pages for authentication, course discovery, learning, and user dashboard.
This commit is contained in:
parent
c4f68eb927
commit
9629f79c52
5 changed files with 13 additions and 65 deletions
|
|
@ -119,10 +119,7 @@ const handleLogin = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional: social login placeholder (ไม่แตะ API เดิม)
|
|
||||||
const handleGoogleLogin = () => {
|
|
||||||
// TODO: implement when backend ready
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const savedEmail = localStorage.getItem('remembered_email')
|
const savedEmail = localStorage.getItem('remembered_email')
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const showAllCategories = ref(false);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { fetchCategories } = useCategory();
|
const { fetchCategories } = useCategory();
|
||||||
const { fetchCourses, fetchCourseById, enrollCourse } = useCourse();
|
const { fetchCourses, fetchCourseById, enrollCourse } = useCourse();
|
||||||
const { currentUser } = useAuth();
|
// const { currentUser } = useAuth() // Unused
|
||||||
|
|
||||||
// 2. Computed Properties
|
// 2. Computed Properties
|
||||||
const sortOption = ref(computed(() => t('discovery.sortRecent')));
|
const sortOption = ref(computed(() => t('discovery.sortRecent')));
|
||||||
|
|
@ -321,7 +321,7 @@ onMounted(() => {
|
||||||
<div class="bg-white rounded-3xl p-6 shadow-lg border border-slate-100 sticky top-24">
|
<div class="bg-white rounded-3xl p-6 shadow-lg border border-slate-100 sticky top-24">
|
||||||
<q-btn
|
<q-btn
|
||||||
@click="handleEnroll(selectedCourse.id)"
|
@click="handleEnroll(selectedCourse.id)"
|
||||||
unetab
|
|
||||||
unelevated
|
unelevated
|
||||||
rounded
|
rounded
|
||||||
class="w-full py-3 text-lg font-bold bg-gradient-to-r from-blue-600 to-indigo-600 text-white shadow-lg"
|
class="w-full py-3 text-lg font-bold bg-gradient-to-r from-blue-600 to-indigo-600 text-white shadow-lg"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
const { fetchCourseLearningInfo, fetchLessonContent, saveVideoProgress, markLessonComplete, checkLessonAccess, fetchVideoProgress } = useCourse()
|
const { fetchCourseLearningInfo, fetchLessonContent, saveVideoProgress, checkLessonAccess, fetchVideoProgress } = useCourse()
|
||||||
// Media Prefs (Global Volume)
|
// Media Prefs (Global Volume)
|
||||||
const { volume, muted: isMuted, setVolume, setMuted, applyTo } = useMediaPrefs()
|
const { volume, muted: isMuted, setVolume, setMuted, applyTo } = useMediaPrefs()
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ const isPlaying = ref(false)
|
||||||
const videoProgress = ref(0)
|
const videoProgress = ref(0)
|
||||||
const currentTime = ref(0)
|
const currentTime = ref(0)
|
||||||
const duration = ref(0)
|
const duration = ref(0)
|
||||||
const saveProgressInterval = ref<any>(null) // ตัวแปรเก็บ setInterval สำหรับบันทึกความคืบหน้าอัตโนมัติ
|
// const saveProgressInterval = ref<any>(null) // Removed unused interval
|
||||||
|
|
||||||
// Helper for localization
|
// Helper for localization
|
||||||
const getLocalizedText = (text: any) => {
|
const getLocalizedText = (text: any) => {
|
||||||
|
|
@ -351,7 +351,7 @@ const videoSrc = computed(() => {
|
||||||
// 3. ระบบบันทึกความคืบหน้า (Progress Tracking)
|
// 3. ระบบบันทึกความคืบหน้า (Progress Tracking)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// บันทึกอัตโนมัติทุกๆ 10 วินาทีเมื่อเล่นวิดีโอ
|
// บันทึกอัตโนมัติทุกๆ 10 วินาทีเมื่อเล่นวิดีโอ
|
||||||
// บันทึกอัตโนมัติทุกๆ 10 วินาทีเมื่อเล่นวิดีโอ
|
|
||||||
// Event Listeners for Robustness
|
// Event Listeners for Robustness
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Page/Tab Visibility Logic
|
// Page/Tab Visibility Logic
|
||||||
|
|
@ -404,31 +404,15 @@ watch(isPlaying, (playing) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// เมื่อวิดีโอจบ ให้บันทึกว่าเรียนจบ (Complete)
|
// เมื่อวิดีโอจบ ให้บันทึกว่าเรียนจบ (Complete)
|
||||||
// เมื่อวิดีโอจบ ให้บันทึกว่าเรียนจบ (Complete)
|
|
||||||
const onVideoEnded = async () => {
|
const onVideoEnded = async () => {
|
||||||
isPlaying.value = false
|
isPlaying.value = false
|
||||||
|
|
||||||
// Force save progress at 100%
|
// Force save progress at 100%
|
||||||
await performSaveProgress(true, false)
|
await performSaveProgress(true, false)
|
||||||
|
|
||||||
// Call explicit complete endpoint if exists
|
|
||||||
// Call explicit complete endpoint if exists
|
// Call explicit complete endpoint if exists
|
||||||
// REMOVED: User requested to remove explicit complete call
|
// REMOVED: User requested to remove explicit complete call
|
||||||
/*
|
|
||||||
if (currentLesson.value) {
|
|
||||||
const res = await markLessonComplete(courseId.value, currentLesson.value.id)
|
|
||||||
if (res.success) {
|
|
||||||
markLessonAsCompletedLocally(currentLesson.value.id)
|
|
||||||
|
|
||||||
// If course completed
|
|
||||||
if (res.data.is_course_completed) {
|
|
||||||
// Refresh course data to update certificate status
|
|
||||||
await loadCourseData()
|
|
||||||
alert(t('course.completed') || "ยินดีด้วย! คุณเรียนจบหลักสูตรแล้ว")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Just refresh local state assuming server handles completion via progress
|
// Just refresh local state assuming server handles completion via progress
|
||||||
if (currentLesson.value) {
|
if (currentLesson.value) {
|
||||||
|
|
@ -457,7 +441,7 @@ onMounted(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
clearInterval(saveProgressInterval.value)
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ useHead({
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const showEnrollModal = ref(false)
|
const showEnrollModal = ref(false)
|
||||||
const activeFilter = ref<'all' | 'progress' | 'completed'>('all')
|
const activeFilter = ref<'all' | 'progress' | 'completed'>('all')
|
||||||
const { currentUser } = useAuth()
|
// const { currentUser } = useAuth() // Unused
|
||||||
|
|
||||||
// Check URL query parameters to show 'Enrollment Success' modal
|
// Check URL query parameters to show 'Enrollment Success' modal
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
@ -87,7 +87,7 @@ onMounted(() => {
|
||||||
// Certificate Handling
|
// Certificate Handling
|
||||||
const downloadingCourseId = ref<number | null>(null)
|
const downloadingCourseId = ref<number | null>(null)
|
||||||
// Certificate Handling
|
// Certificate Handling
|
||||||
// Certificate Handling
|
|
||||||
const downloadCertificate = async (course: any) => {
|
const downloadCertificate = async (course: any) => {
|
||||||
if (!course) return
|
if (!course) return
|
||||||
downloadingCourseId.value = course.id
|
downloadingCourseId.value = course.id
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ useHead({
|
||||||
|
|
||||||
const { currentUser, updateUserProfile, changePassword, uploadAvatar } = useAuth()
|
const { currentUser, updateUserProfile, changePassword, uploadAvatar } = useAuth()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { errors, validate, clearFieldError } = useFormValidation()
|
// const { errors, validate, clearFieldError } = useFormValidation() // Unused
|
||||||
|
|
||||||
|
|
||||||
const isEditing = ref(false)
|
const isEditing = ref(false)
|
||||||
const isProfileSaving = ref(false)
|
const isProfileSaving = ref(false)
|
||||||
|
|
@ -106,41 +107,7 @@ const handleFileUpload = async (event: Event) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteAvatar = async () => {
|
|
||||||
// 1. Clear local preview
|
|
||||||
userData.value.photoURL = ''
|
|
||||||
|
|
||||||
// 2. Update Auth State
|
|
||||||
if (currentUser.value?.role) {
|
|
||||||
// Direct manipulation since useAuth is shared state
|
|
||||||
if (currentUser.value.photoURL) {
|
|
||||||
// We can't set currentUser directly as it is computed.
|
|
||||||
// We must access the underlying user state from useAuth?
|
|
||||||
// But we only destructured what we needed.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Access the user raw ref from useAuth would be better but we didn't destructure it.
|
|
||||||
// However, we can use a workaround: call updateUserProfile with empty avatar if supported OR just assume local state.
|
|
||||||
// The user requirement is: "If delete is clicked ... display q-avatar__content".
|
|
||||||
// Since we don't have a confirmed DELETE API, we will just update the visual state.
|
|
||||||
// To make it persist somewhat (until refresh), we update the cookie via a helper if possible,
|
|
||||||
// BUT since we don't have access to `user` ref here (only currentUser computed),
|
|
||||||
// let's grab `user` from useAuth() again or add it to destructure.
|
|
||||||
|
|
||||||
// Actually, I'll allow this component to just visual clear for now,
|
|
||||||
// until we confirm API. But wait, `uploadAvatar` updates the global user state.
|
|
||||||
// We should probably export `user` from useAuth or Add a `clearAvatar` method to useAuth.
|
|
||||||
// For now, let's keep it simple in the component:
|
|
||||||
|
|
||||||
// TODO: Call API to delete avatar if endpoint exists.
|
|
||||||
// For now, visual clear.
|
|
||||||
|
|
||||||
const { user } = useAuth() // Re-access to get the raw user ref
|
|
||||||
if (user.value && user.value.profile) {
|
|
||||||
user.value.profile.avatar_url = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUpdateProfile = async () => {
|
const handleUpdateProfile = async () => {
|
||||||
isProfileSaving.value = true
|
isProfileSaving.value = true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue