feat: Implement course discovery page with browsing, filtering, and detail view, powered by a new useCourse composable.
This commit is contained in:
parent
e6a73c836c
commit
122bb2332f
2 changed files with 55 additions and 5 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue