11<template >
22 <div
3- class =" course-card hover:shadow-lg transition duration-300 rounded-2xl overflow-hidden border border-gray-300 bg-white flex flex-col"
3+ class =" course-card relative hover:shadow-lg transition duration-300 rounded-2xl overflow-hidden border border-gray-300 bg-white flex flex-col"
44 >
5+ <div
6+ v-if =" course.categories?.length"
7+ class =" absolute top-2 left-2 flex flex-wrap gap-1 z-30"
8+ >
9+ <span
10+ v-for =" cat in course.categories"
11+ :key =" cat.id"
12+ class =" bg-support-5 text-white text-xs font-bold px-2 py-0.5 rounded"
13+ >
14+ {{ cat.title }}
15+ </span >
16+ </div >
17+ <span
18+ v-if =" course.courseLanguage"
19+ class =" absolute top-0 right-0 bg-support-4 text-white text-xs px-2 py-0.5 font-semibold rounded-bl-lg z-20"
20+ >
21+ {{ course.courseLanguage }}
22+ </span >
23+
24+ <Button
25+ v-if =" allowDescription && showInfoPopup"
26+ icon =" pi pi-info-circle"
27+ @click =" showDescriptionDialog = true"
28+ class =" absolute top-10 left-2 z-20"
29+ size =" small"
30+ text
31+ aria-label =" Course info"
32+ />
33+ <router-link
34+ v-if =" imageLink"
35+ :to =" imageLink"
36+ >
37+ <img
38+ :src =" course.illustrationUrl"
39+ :alt =" course.title"
40+ class =" w-full object-cover"
41+ />
42+ </router-link >
543 <img
44+ v-else
645 :src =" course.illustrationUrl"
746 :alt =" course.title"
847 class =" w-full object-cover"
948 />
1049 <div class =" p-4 flex flex-col flex-grow gap-2" >
11- <h3 class =" text-xl font-semibold text-gray-800" >{{ course.title }}</h3 >
12- <p class =" text-sm text-gray-600 line-clamp-3" >{{ course.description }}</p >
13-
50+ <router-link
51+ v-if =" showTitle && titleLink"
52+ :to =" titleLink"
53+ class =" text-xl font-semibold"
54+ >
55+ {{ course.title }}
56+ </router-link >
57+ <h3
58+ v-else-if =" showTitle"
59+ class =" text-xl font-semibold"
60+ >
61+ {{ course.title }}
62+ </h3 >
1463 <div
1564 v-if =" course.duration"
1665 class =" text-sm text-gray-700"
3382 <strong >{{ $t("Price") }}:</strong >
3483 {{ course.price > 0 ? "S/. " + course.price.toFixed(2) : $t("Free") }}
3584 </div >
36-
37- <div
38- v-if =" course.categories?.length"
39- class =" flex flex-wrap gap-1"
40- >
41- <span
42- v-for =" cat in course.categories"
43- :key =" cat.id"
44- class =" text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full"
45- >
46- {{ cat.title }}
47- </span >
48- </div >
49-
50- <div class =" text-sm text-gray-700" >
51- <strong >{{ $t("Language") }}:</strong > {{ course.courseLanguage }}
52- </div >
53-
5485 <div
5586 v-if =" course.teachers?.length"
5687 class =" text-sm text-gray-700"
5788 >
5889 <strong >{{ $t("Teachers") }}:</strong >
5990 {{ course.teachers.map((t) => t.user.fullName).join(", ") }}
6091 </div >
61-
6292 <Rating
93+ v-if =" props.currentUserId"
6394 :model-value =" course.userVote?.vote || 0"
6495 :stars =" 5"
6596 :cancel =" false"
79110 </span >
80111 </div >
81112
113+ <div
114+ v-for =" field in cardExtraFields"
115+ :key =" field.variable"
116+ class =" text-sm text-gray-700"
117+ >
118+ <strong >{{ field.display_text }}:</strong >
119+ {{ course.extra_fields?.[field.variable] ?? "-" }}
120+ </div >
121+
82122 <div class =" mt-auto pt-2" >
83123 <router-link
84124 v-if =" course.visibility === 3 || (course.visibility === 2 && isUserInCourse)"
125165 </div >
126166 </div >
127167 </div >
168+ <Dialog
169+ v-model:visible =" showDescriptionDialog"
170+ :header =" course.title"
171+ modal
172+ class =" w-96"
173+ >
174+ <p class =" text-sm text-gray-700 whitespace-pre-line" >
175+ {{ course.description || $t("No description available") }}
176+ </p >
177+ </Dialog >
128178</template >
129179<script setup>
130180import Rating from " primevue/rating"
131181import Button from " primevue/button"
132182import { computed , ref } from " vue"
133183import courseRelUserService from " ../../services/courseRelUserService"
134- import { useRouter } from " vue-router"
184+ import { useRoute , useRouter } from " vue-router"
135185import { useNotification } from " ../../composables/notification"
186+ import Dialog from " primevue/dialog"
187+ import { usePlatformConfig } from " ../../store/platformConfig"
188+
189+ const platformConfigStore = usePlatformConfig ()
190+ const showDescriptionDialog = ref (false )
191+
192+ const allowDescription = computed (
193+ () => platformConfigStore .getSetting (" course.show_courses_descriptions_in_catalog" ) !== " false" ,
194+ )
136195
137196const props = defineProps ({
138197 course: Object ,
139- currentUserId: Number ,
198+ currentUserId: {
199+ type: Number ,
200+ default: null ,
201+ },
202+ showTitle: {
203+ type: Boolean ,
204+ default: true ,
205+ },
206+ cardExtraFields: { type: Array , default : () => [] },
140207})
141208
142209const emit = defineEmits ([" rate" , " subscribed" ])
143210
144211const router = useRouter ()
212+ const route = useRoute ()
145213const { showErrorNotification , showSuccessNotification } = useNotification ()
146214
147215const isUserInCourse = computed (() => {
216+ if (! props .currentUserId ) return false
148217 return props .course .users ? .some ((user ) => user .user .id === props .currentUserId )
149218})
150219
@@ -160,21 +229,108 @@ const emitRating = (event) => {
160229
161230const subscribing = ref (false )
162231const subscribeToCourse = async () => {
232+ if (! props .currentUserId ) {
233+ showErrorNotification (" You must be logged in to subscribe to a course." )
234+ return
235+ }
236+
163237 try {
164238 subscribing .value = true
165239
166- const response = await courseRelUserService .subscribe ({
167- userId: props .currentUserId ,
168- courseId: props .course .id ,
169- })
240+ const useAutoSession =
241+ platformConfigStore .getSetting (" session.catalog_course_subscription_in_user_s_session" ) === " true"
242+
243+ let sessionId = null
244+
245+ if (useAutoSession) {
246+ const response = await courseRelUserService .autoSubscribeCourse (props .course .id )
247+ sessionId = response? .sessionId
248+
249+ if (! sessionId) {
250+ throw new Error (" No session ID returned after subscription." )
251+ }
252+ } else {
253+ const response = await courseRelUserService .subscribe ({
254+ userId: props .currentUserId ,
255+ courseId: props .course .id ,
256+ })
257+
258+ const userIdFromResponse = response? .user ? .[" @id" ]? .split (" /" )? .pop ()
259+
260+ emit (" subscribed" , {
261+ courseId: props .course .id ,
262+ newUser: { user: { id: Number (userIdFromResponse) } },
263+ })
264+ }
170265
171- emit (" subscribed" , { courseId: props .course .id , newUser: response })
172266 showSuccessNotification (" You have successfully subscribed to this course." )
173- router .push ({ name: " CourseHome" , params: { id: props .course .id } })
267+
268+ await router .push ({
269+ name: " CourseHome" ,
270+ params: {
271+ id: props .course .id ,
272+ },
273+ query: sessionId ? { sid: sessionId } : {},
274+ })
174275 } catch (e) {
276+ console .error (" Subscription error:" , e)
175277 showErrorNotification (" Failed to subscribe to the course." )
176278 } finally {
177279 subscribing .value = false
178280 }
179281}
282+
283+ function routeExists (name ) {
284+ return router .getRoutes ().some ((route ) => route .name === name)
285+ }
286+
287+ const linkSettings = computed (() => {
288+ const settings = platformConfigStore .getSetting (" course.course_catalog_settings" )
289+ const result = settings? .link_settings ?? {}
290+ console .log (" Link settings:" , result)
291+ return result
292+ })
293+
294+ const imageLink = computed (() => {
295+ const routeName =
296+ linkSettings .value .image_url === " course_home"
297+ ? " CourseHome"
298+ : linkSettings .value .image_url === " course_about"
299+ ? " CourseAbout"
300+ : null
301+
302+ if (routeName && routeExists (routeName)) {
303+ return { name: routeName, params: { id: props .course .id } }
304+ }
305+
306+ if (routeName) {
307+ console .warn (` [CatalogueCourseCard] Route '${ routeName} ' does not exist.` )
308+ }
309+
310+ return null
311+ })
312+
313+ const titleLink = computed (() => {
314+ const routeName = linkSettings .value .title_url === " course_home" ? " CourseHome" : null
315+
316+ if (routeName && routeExists (routeName)) {
317+ return { name: routeName, params: { id: props .course .id } }
318+ }
319+
320+ if (routeName) {
321+ console .warn (` [CatalogueCourseCard] Route '${ routeName} ' does not exist.` )
322+ }
323+
324+ return null
325+ })
326+
327+ const showInfoPopup = computed (() => {
328+ const allowed = [" course_description_popup" ]
329+ const value = linkSettings .value .info_url
330+ if (value && ! allowed .includes (value)) {
331+ console .warn (` [CatalogueCourseCard] info_url '${ value} ' is not a recognized option.` )
332+ return false
333+ }
334+ return value === " course_description_popup"
335+ })
180336< / script>
0 commit comments