feat: Add core e-learning pages for authentication, course discovery, learning, and user dashboard.

This commit is contained in:
supalerk-ar66 2026-01-30 14:42:08 +07:00
parent c4f68eb927
commit 9629f79c52
5 changed files with 13 additions and 65 deletions

View file

@ -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')

View file

@ -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"

View file

@ -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>

View file

@ -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

View file

@ -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