feat: Implement course discovery page with browsing, filtering, and detail view, powered by a new useCourse composable.

This commit is contained in:
supalerk-ar66 2026-01-20 15:01:01 +07:00
parent e6a73c836c
commit 122bb2332f
2 changed files with 55 additions and 5 deletions

View file

@ -88,9 +88,34 @@ export const useCourse = () => {
} }
} }
const enrollCourse = async (courseId: number) => {
try {
const data = await $fetch<{ code: number; message: string; data: any }>(`${API_BASE_URL}/students/courses/${courseId}/enroll`, {
method: 'POST',
headers: token.value ? {
Authorization: `Bearer ${token.value}`
} : {}
})
return {
success: true,
data: data.data,
message: data.message
}
} catch (err: any) {
console.error('Enroll course failed:', err)
return {
success: false,
error: err.data?.message || err.message || 'Error enrolling in course',
code: err.data?.code
}
}
}
return { return {
fetchCourses, fetchCourses,
fetchCourseById fetchCourseById,
enrollCourse
} }
} }

View file

@ -44,11 +44,12 @@ const visibleCategories = computed(() => {
}); });
// Courses Data // Courses Data
const { fetchCourses, fetchCourseById } = useCourse(); const { fetchCourses, fetchCourseById, enrollCourse } = useCourse();
const courses = ref<any[]>([]); const courses = ref<any[]>([]);
const isLoading = ref(false); const isLoading = ref(false);
const selectedCourse = ref<any>(null); const selectedCourse = ref<any>(null);
const isLoadingDetail = ref(false); const isLoadingDetail = ref(false);
const isEnrolling = ref(false);
const loadCourses = async () => { const loadCourses = async () => {
isLoading.value = true; isLoading.value = true;
@ -76,6 +77,28 @@ const selectCourse = async (id: number) => {
isLoadingDetail.value = false; isLoadingDetail.value = false;
}; };
const handleEnroll = async (id: number) => {
if (isEnrolling.value) return;
isEnrolling.value = true;
const res = await enrollCourse(id);
if (res.success) {
// Navigate to my-courses where the success modal will be shown
return navigateTo('/dashboard/my-courses?enrolled=true');
} else if (res.code === 409) {
// Already enrolled, maybe redirect to course learning page or my-courses?
// For now, let's redirect to my-courses as well, maybe with a different param or just same.
// User requirement didn't specify distinct behavior for 409 in UI terms,
// but 'Already enrolled' implies they should go see it.
return navigateTo('/dashboard/my-courses');
} else {
alert(res.error || 'Failed to enroll');
}
isEnrolling.value = false;
};
onMounted(() => { onMounted(() => {
loadCategories(); loadCategories();
loadCourses(); loadCourses();
@ -416,13 +439,15 @@ const filteredCourses = computed(() => {
</h2> </h2>
</div> </div>
<NuxtLink <button
:to="`/dashboard/my-courses?enroll=${selectedCourse.id}`" @click="handleEnroll(selectedCourse.id)"
class="btn btn-primary w-full mb-4 text-white" class="btn btn-primary w-full mb-4 text-white"
style="height: 48px; font-size: 16px" style="height: 48px; font-size: 16px"
:disabled="isEnrolling"
> >
<span v-if="isEnrolling" class="spinner-border animate-spin inline-block w-4 h-4 border-2 rounded-full mr-2"></span>
{{ $t('course.enrollNow') }} {{ $t('course.enrollNow') }}
</NuxtLink> </button>
<div class="text-sm text-slate-600 dark:text-slate-400 mb-4"> <div class="text-sm text-slate-600 dark:text-slate-400 mb-4">
<div <div