+
+
+ {{ __('Payment for ') }} {{ type }}:
+
+
+ {{ orderSummary.data.title }}
+
-
- {{ orderSummary.data.title }}
+
+
+ {{ __('Original Amount') }}:
+
+
+ {{ orderSummary.data.original_amount_formatted }}
+
+
+
+
{{ __('Discount') }}:
+
- {{ orderSummary.data.discount_amount_formatted }}
+
+
+
+ {{ __('GST Amount') }}:
+
+
+ {{ orderSummary.data.gst_amount_formatted }}
+
+
+
+
+ {{ __('Total') }}:
+
+
+ {{ orderSummary.data.total_amount_formatted }}
+
-
-
- {{ __('Original Amount') }}
-
-
- {{ orderSummary.data.original_amount_formatted }}
-
-
-
-
- {{ __('GST Amount') }}
-
-
- {{ orderSummary.data.gst_amount_formatted }}
-
-
-
-
- {{ __('Total') }}
-
-
- {{ orderSummary.data.total_amount_formatted }}
+
+
+
+ {{ __('Enter a Coupon Code') }}:
+
+
+
+
+
@@ -112,7 +147,7 @@
/>
@@ -157,11 +192,13 @@ import {
Breadcrumbs,
usePageMeta,
toast,
+ call,
} from 'frappe-ui'
-import { reactive, inject, onMounted, computed } from 'vue'
+import { reactive, inject, onMounted, computed, ref } from 'vue'
import { sessionStore } from '../stores/session'
import Link from '@/components/Controls/Link.vue'
import NotPermitted from '@/components/NotPermitted.vue'
+import { X } from 'lucide-vue-next'
const user = inject('$user')
const { brand } = sessionStore()
@@ -205,6 +242,7 @@ const orderSummary = createResource({
doctype: props.type == 'batch' ? 'LMS Batch' : 'LMS Course',
docname: props.name,
country: billingDetails.country,
+ coupon: appliedCoupon.value,
}
},
onError(err) {
@@ -212,6 +250,7 @@ const orderSummary = createResource({
},
})
+const appliedCoupon = ref(null)
const billingDetails = reactive({})
const setBillingDetails = (data) => {
@@ -231,17 +270,21 @@ const setBillingDetails = (data) => {
const paymentLink = createResource({
url: 'lms.lms.payments.get_payment_link',
makeParams(values) {
- return {
+ let data = {
doctype: props.type == 'batch' ? 'LMS Batch' : 'LMS Course',
docname: props.name,
title: orderSummary.data.title,
amount: orderSummary.data.original_amount,
- total_amount: orderSummary.data.amount,
+ discount_amount: orderSummary.data.discount_amount || 0,
+ gst_amount: orderSummary.data.gst_applied || 0,
currency: orderSummary.data.currency,
address: billingDetails,
redirect_to: redirectTo.value,
payment_for_certificate: props.type == 'certificate',
+ coupon_code: appliedCoupon.value,
+ coupon: orderSummary.data.coupon,
}
+ return data
},
})
@@ -265,6 +308,19 @@ const generatePaymentLink = () => {
)
}
+function applyCouponCode() {
+ if (!appliedCoupon.value) {
+ toast.error(__('Please enter a coupon code'))
+ return
+ }
+ orderSummary.reload()
+}
+
+function removeCoupon() {
+ appliedCoupon.value = null
+ orderSummary.reload()
+}
+
const validateAddress = () => {
let mandatoryFields = [
'billing_name',
@@ -329,8 +385,6 @@ const validateAddress = () => {
!states.includes(billingDetails.state)
)
return 'Please enter a valid state with correct spelling and the first letter capitalized.'
-
- console.log('validation address')
}
const showError = (err) => {
diff --git a/frontend/src/pages/CertifiedParticipants.vue b/frontend/src/pages/CertifiedParticipants.vue
index f61c4c5b..10230c3c 100644
--- a/frontend/src/pages/CertifiedParticipants.vue
+++ b/frontend/src/pages/CertifiedParticipants.vue
@@ -124,7 +124,7 @@ const memberCount = ref(0)
const dayjs = inject('$dayjs')
onMounted(() => {
- getMemberCount()
+ setFiltersFromQuery()
updateParticipants()
})
@@ -158,6 +158,8 @@ const categories = createListResource({
const updateParticipants = () => {
updateFilters()
getMemberCount()
+ setQueryParams()
+
participants.update({
filters: filters.value,
})
@@ -178,6 +180,33 @@ const updateFilters = () => {
}
}
+const setQueryParams = () => {
+ let queries = new URLSearchParams(location.search)
+ let filterKeys = {
+ category: currentCategory.value,
+ name: nameFilter.value,
+ }
+
+ Object.keys(filterKeys).forEach((key) => {
+ if (filterKeys[key]) {
+ queries.set(key, filterKeys[key])
+ } else {
+ queries.delete(key)
+ }
+ })
+ history.replaceState(
+ {},
+ '',
+ `${location.pathname}${queries.size > 0 ? `?${queries.toString()}` : ''}`
+ )
+}
+
+const setFiltersFromQuery = () => {
+ let queries = new URLSearchParams(location.search)
+ nameFilter.value = queries.get('name') || ''
+ currentCategory.value = queries.get('category') || ''
+}
+
const breadcrumbs = computed(() => [
{
label: __('Certified Members'),
diff --git a/frontend/src/pages/CourseForm.vue b/frontend/src/pages/CourseForm.vue
index fb9b461d..67f6b83d 100644
--- a/frontend/src/pages/CourseForm.vue
+++ b/frontend/src/pages/CourseForm.vue
@@ -357,6 +357,7 @@ import {
getMetaInfo,
updateMetaInfo,
validateFile,
+ escapeHTML,
} from '@/utils'
import Link from '@/components/Controls/Link.vue'
import CourseOutline from '@/components/CourseOutline.vue'
@@ -537,7 +538,16 @@ const imageResource = createResource({
},
})
+const validateFields = () => {
+ Object.keys(course).forEach((key) => {
+ if (key != 'description' && typeof course[key] === 'string') {
+ course[key] = escapeHTML(course[key])
+ }
+ })
+}
+
const submitCourse = () => {
+ validateFields()
if (courseResource.data) {
editCourse()
} else {
diff --git a/frontend/src/pages/Home/AdminHome.vue b/frontend/src/pages/Home/AdminHome.vue
index 5a4d4c51..f0a8e70c 100644
--- a/frontend/src/pages/Home/AdminHome.vue
+++ b/frontend/src/pages/Home/AdminHome.vue
@@ -94,10 +94,10 @@
-
+
{{ evaluation.course_title }}
@@ -128,8 +128,11 @@
{{ __('Upcoming Live Classes') }}
-
-
+
+
{{ cls.title }}
diff --git a/frontend/src/pages/Home/StudentHome.vue b/frontend/src/pages/Home/StudentHome.vue
index babc0dd3..951917a8 100644
--- a/frontend/src/pages/Home/StudentHome.vue
+++ b/frontend/src/pages/Home/StudentHome.vue
@@ -71,8 +71,11 @@
{{ __('Upcoming Live Classes') }}
-
-
+
+
{{ cls.title }}
diff --git a/frontend/src/pages/Profile.vue b/frontend/src/pages/Profile.vue
index 74a8d420..346bf021 100644
--- a/frontend/src/pages/Profile.vue
+++ b/frontend/src/pages/Profile.vue
@@ -2,9 +2,17 @@
![]()
import {
Breadcrumbs,
- createResource,
Button,
+ call,
+ createResource,
TabButtons,
usePageMeta,
} from 'frappe-ui'
import { computed, inject, watch, ref, onMounted, watchEffect } from 'vue'
import { sessionStore } from '@/stores/session'
-import { Edit } from 'lucide-vue-next'
-import UserAvatar from '@/components/UserAvatar.vue'
+import { Edit, RefreshCcw } from 'lucide-vue-next'
import { useRoute, useRouter } from 'vue-router'
-import NoPermission from '@/components/NoPermission.vue'
import { convertToTitleCase } from '@/utils'
+import UserAvatar from '@/components/UserAvatar.vue'
+import NoPermission from '@/components/NoPermission.vue'
import EditProfile from '@/components/Modals/EditProfile.vue'
import EditCoverImage from '@/components/Modals/EditCoverImage.vue'
@@ -212,6 +221,14 @@ const getTabButtons = () => {
return buttons
}
+const reloadUser = () => {
+ call('frappe.sessions.clear').then(() => {
+ $user.reload().then(() => {
+ profile.reload()
+ })
+ })
+}
+
const breadcrumbs = computed(() => {
let crumbs = [
{
diff --git a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseForm.vue b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseForm.vue
index 5a6314e5..2a40361d 100644
--- a/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseForm.vue
+++ b/frontend/src/pages/ProgrammingExercises/ProgrammingExerciseForm.vue
@@ -105,6 +105,8 @@