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 {
fetchCourses,
fetchCourseById
fetchCourseById,
enrollCourse
}
}

View file

@ -44,11 +44,12 @@ const visibleCategories = computed(() => {
});
// Courses Data
const { fetchCourses, fetchCourseById } = useCourse();
const { fetchCourses, fetchCourseById, enrollCourse } = useCourse();
const courses = ref<any[]>([]);
const isLoading = ref(false);
const selectedCourse = ref<any>(null);
const isLoadingDetail = ref(false);
const isEnrolling = ref(false);
const loadCourses = async () => {
isLoading.value = true;
@ -76,6 +77,28 @@ const selectCourse = async (id: number) => {
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(() => {
loadCategories();
loadCourses();
@ -416,13 +439,15 @@ const filteredCourses = computed(() => {
</h2>
</div>
<NuxtLink
:to="`/dashboard/my-courses?enroll=${selectedCourse.id}`"
<button
@click="handleEnroll(selectedCourse.id)"
class="btn btn-primary w-full mb-4 text-white"
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') }}
</NuxtLink>
</button>
<div class="text-sm text-slate-600 dark:text-slate-400 mb-4">
<div