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(() => {
|
||||
const savedEmail = localStorage.getItem('remembered_email')
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const showAllCategories = ref(false);
|
|||
const { t } = useI18n();
|
||||
const { fetchCategories } = useCategory();
|
||||
const { fetchCourses, fetchCourseById, enrollCourse } = useCourse();
|
||||
const { currentUser } = useAuth();
|
||||
// const { currentUser } = useAuth() // Unused
|
||||
|
||||
// 2. Computed Properties
|
||||
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">
|
||||
<q-btn
|
||||
@click="handleEnroll(selectedCourse.id)"
|
||||
unetab
|
||||
|
||||
unelevated
|
||||
rounded
|
||||
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 { t } = useI18n()
|
||||
const { user } = useAuth()
|
||||
const { fetchCourseLearningInfo, fetchLessonContent, saveVideoProgress, markLessonComplete, checkLessonAccess, fetchVideoProgress } = useCourse()
|
||||
const { fetchCourseLearningInfo, fetchLessonContent, saveVideoProgress, checkLessonAccess, fetchVideoProgress } = useCourse()
|
||||
// Media Prefs (Global Volume)
|
||||
const { volume, muted: isMuted, setVolume, setMuted, applyTo } = useMediaPrefs()
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ const isPlaying = ref(false)
|
|||
const videoProgress = ref(0)
|
||||
const currentTime = ref(0)
|
||||
const duration = ref(0)
|
||||
const saveProgressInterval = ref<any>(null) // ตัวแปรเก็บ setInterval สำหรับบันทึกความคืบหน้าอัตโนมัติ
|
||||
// const saveProgressInterval = ref<any>(null) // Removed unused interval
|
||||
|
||||
// Helper for localization
|
||||
const getLocalizedText = (text: any) => {
|
||||
|
|
@ -351,7 +351,7 @@ const videoSrc = computed(() => {
|
|||
// 3. ระบบบันทึกความคืบหน้า (Progress Tracking)
|
||||
// ==========================================
|
||||
// บันทึกอัตโนมัติทุกๆ 10 วินาทีเมื่อเล่นวิดีโอ
|
||||
// บันทึกอัตโนมัติทุกๆ 10 วินาทีเมื่อเล่นวิดีโอ
|
||||
|
||||
// Event Listeners for Robustness
|
||||
onMounted(() => {
|
||||
// Page/Tab Visibility Logic
|
||||
|
|
@ -404,31 +404,15 @@ watch(isPlaying, (playing) => {
|
|||
})
|
||||
|
||||
// เมื่อวิดีโอจบ ให้บันทึกว่าเรียนจบ (Complete)
|
||||
// เมื่อวิดีโอจบ ให้บันทึกว่าเรียนจบ (Complete)
|
||||
|
||||
const onVideoEnded = async () => {
|
||||
isPlaying.value = false
|
||||
|
||||
// Force save progress at 100%
|
||||
await performSaveProgress(true, false)
|
||||
|
||||
// Call explicit complete endpoint if exists
|
||||
// Call explicit complete endpoint if exists
|
||||
// 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
|
||||
if (currentLesson.value) {
|
||||
|
|
@ -457,7 +441,7 @@ onMounted(() => {
|
|||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(saveProgressInterval.value)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ useHead({
|
|||
const route = useRoute()
|
||||
const showEnrollModal = ref(false)
|
||||
const activeFilter = ref<'all' | 'progress' | 'completed'>('all')
|
||||
const { currentUser } = useAuth()
|
||||
// const { currentUser } = useAuth() // Unused
|
||||
|
||||
// Check URL query parameters to show 'Enrollment Success' modal
|
||||
onMounted(() => {
|
||||
|
|
@ -87,7 +87,7 @@ onMounted(() => {
|
|||
// Certificate Handling
|
||||
const downloadingCourseId = ref<number | null>(null)
|
||||
// Certificate Handling
|
||||
// Certificate Handling
|
||||
|
||||
const downloadCertificate = async (course: any) => {
|
||||
if (!course) return
|
||||
downloadingCourseId.value = course.id
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ useHead({
|
|||
|
||||
const { currentUser, updateUserProfile, changePassword, uploadAvatar } = useAuth()
|
||||
const { t } = useI18n()
|
||||
const { errors, validate, clearFieldError } = useFormValidation()
|
||||
// const { errors, validate, clearFieldError } = useFormValidation() // Unused
|
||||
|
||||
|
||||
const isEditing = 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 () => {
|
||||
isProfileSaving.value = true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue