1
1
<template >
2
2
<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"
4
4
>
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 >
5
43
<img
44
+ v-else
6
45
:src =" course.illustrationUrl"
7
46
:alt =" course.title"
8
47
class =" w-full object-cover"
9
48
/>
10
49
<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 >
14
63
<div
15
64
v-if =" course.duration"
16
65
class =" text-sm text-gray-700"
33
82
<strong >{{ $t("Price") }}:</strong >
34
83
{{ course.price > 0 ? "S/. " + course.price.toFixed(2) : $t("Free") }}
35
84
</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
-
54
85
<div
55
86
v-if =" course.teachers?.length"
56
87
class =" text-sm text-gray-700"
57
88
>
58
89
<strong >{{ $t("Teachers") }}:</strong >
59
90
{{ course.teachers.map((t) => t.user.fullName).join(", ") }}
60
91
</div >
61
-
62
92
<Rating
93
+ v-if =" props.currentUserId"
63
94
:model-value =" course.userVote?.vote || 0"
64
95
:stars =" 5"
65
96
:cancel =" false"
79
110
</span >
80
111
</div >
81
112
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
+
82
122
<div class =" mt-auto pt-2" >
83
123
<router-link
84
124
v-if =" course.visibility === 3 || (course.visibility === 2 && isUserInCourse)"
125
165
</div >
126
166
</div >
127
167
</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 >
128
178
</template >
129
179
<script setup>
130
180
import Rating from " primevue/rating"
131
181
import Button from " primevue/button"
132
182
import { computed , ref } from " vue"
133
183
import courseRelUserService from " ../../services/courseRelUserService"
134
- import { useRouter } from " vue-router"
184
+ import { useRoute , useRouter } from " vue-router"
135
185
import { 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
+ )
136
195
137
196
const props = defineProps ({
138
197
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 : () => [] },
140
207
})
141
208
142
209
const emit = defineEmits ([" rate" , " subscribed" ])
143
210
144
211
const router = useRouter ()
212
+ const route = useRoute ()
145
213
const { showErrorNotification , showSuccessNotification } = useNotification ()
146
214
147
215
const isUserInCourse = computed (() => {
216
+ if (! props .currentUserId ) return false
148
217
return props .course .users ? .some ((user ) => user .user .id === props .currentUserId )
149
218
})
150
219
@@ -160,21 +229,108 @@ const emitRating = (event) => {
160
229
161
230
const subscribing = ref (false )
162
231
const subscribeToCourse = async () => {
232
+ if (! props .currentUserId ) {
233
+ showErrorNotification (" You must be logged in to subscribe to a course." )
234
+ return
235
+ }
236
+
163
237
try {
164
238
subscribing .value = true
165
239
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
+ }
170
265
171
- emit (" subscribed" , { courseId: props .course .id , newUser: response })
172
266
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
+ })
174
275
} catch (e) {
276
+ console .error (" Subscription error:" , e)
175
277
showErrorNotification (" Failed to subscribe to the course." )
176
278
} finally {
177
279
subscribing .value = false
178
280
}
179
281
}
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
+ })
180
336
< / script>
0 commit comments