- {{ orderSummary.data.total_amount_formatted }}
+
+
+
+ {{ __('Enter a Coupon Code') }}:
+
+
+
+
+
@@ -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 843d8f40..67f6b83d 100644
--- a/frontend/src/pages/CourseForm.vue
+++ b/frontend/src/pages/CourseForm.vue
@@ -21,7 +21,7 @@
-
+
{{ __('Details') }}
@@ -138,7 +138,7 @@
-
+
{{ __('Settings') }}
@@ -178,7 +178,7 @@
-
+
{{ __('About the Course') }}
-
+
{{ __('Pricing and Certification') }}
@@ -294,7 +294,7 @@
-
+
{{ __('Meta Tags') }}
@@ -329,7 +329,6 @@
diff --git a/frontend/src/pages/JobDetail.vue b/frontend/src/pages/JobDetail.vue
index dea21e94..9df6ffc5 100644
--- a/frontend/src/pages/JobDetail.vue
+++ b/frontend/src/pages/JobDetail.vue
@@ -20,6 +20,17 @@
v-if="user.data?.name && !readOnlyMode"
class="flex items-center space-x-2"
>
+
+
+
-
+
{{ __('Job Details') }}
@@ -59,7 +59,7 @@
-
+
{{ __('Company Details') }}
@@ -158,7 +158,7 @@ import { computed, onMounted, reactive, inject } from 'vue'
import { FileText, X } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
import { useRouter } from 'vue-router'
-import { getFileSize, validateFile } from '@/utils'
+import { escapeHTML, getFileSize, validateFile } from '@/utils'
const user = inject('$user')
const router = useRouter()
@@ -248,6 +248,7 @@ onMounted(() => {
})
const saveJob = () => {
+ validateJobFields()
if (jobDetail.data) {
editJobDetails()
} else {
@@ -293,6 +294,14 @@ const editJobDetails = () => {
)
}
+const validateJobFields = () => {
+ Object.keys(job).forEach((key) => {
+ if (key != 'description' && typeof job[key] === 'string') {
+ job[key] = escapeHTML(job[key])
+ }
+ })
+}
+
const saveImage = (file) => {
job.image = file
}
diff --git a/frontend/src/pages/LessonForm.vue b/frontend/src/pages/LessonForm.vue
index 812ee164..b3a3f8a8 100644
--- a/frontend/src/pages/LessonForm.vue
+++ b/frontend/src/pages/LessonForm.vue
@@ -142,7 +142,6 @@ const renderEditor = (holder) => {
return new EditorJS({
holder: holder,
tools: getEditorTools(true),
- autofocus: true,
defaultBlock: 'markdown',
onChange: async (api, event) => {
enablePlyr()
diff --git a/frontend/src/pages/Notifications.vue b/frontend/src/pages/Notifications.vue
index 020a7cc5..afcfd9a5 100644
--- a/frontend/src/pages/Notifications.vue
+++ b/frontend/src/pages/Notifications.vue
@@ -30,14 +30,14 @@
-
handleMarkAsRead(e, log.name)"
class="text-ink-gray-5 font-medium text-sm hover:text-ink-gray-7"
>
{{ __('View') }}
-
+
![]()
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'
@@ -124,18 +133,14 @@ const props = defineProps({
onMounted(() => {
if ($user.data) profile.reload()
-
setActiveTab()
})
const profile = createResource({
- url: 'frappe.client.get',
- makeParams(values) {
+ url: 'lms.lms.api.get_profile_details',
+ makeParams() {
return {
- doctype: 'User',
- filters: {
- username: props.username,
- },
+ username: props.username,
}
},
})
@@ -191,23 +196,39 @@ const editProfile = () => {
}
const isSessionUser = () => {
- return $user.data?.email === profile.data?.email
+ return $user.data?.email === profile.data?.name
+}
+
+const currentUserHasHigherAccess = () => {
+ return $user.data?.is_evaluator || $user.data?.is_moderator
+}
+
+const isEvaluatorOrModerator = () => {
+ return (
+ profile.data?.roles?.includes('Batch Evaluator') ||
+ profile.data?.roles?.includes('Moderator')
+ )
}
const getTabButtons = () => {
let buttons = [{ label: 'About' }, { label: 'Certificates' }]
if ($user.data?.is_moderator) buttons.push({ label: 'Roles' })
- if (
- isSessionUser() &&
- ($user.data?.is_evaluator || $user.data?.is_moderator)
- ) {
+
+ if (currentUserHasHigherAccess() && isEvaluatorOrModerator()) {
buttons.push({ label: 'Slots' })
buttons.push({ label: 'Schedule' })
}
-
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/ProfileEvaluationSchedule.vue b/frontend/src/pages/ProfileEvaluationSchedule.vue
index 02847f49..9e12f7ab 100644
--- a/frontend/src/pages/ProfileEvaluationSchedule.vue
+++ b/frontend/src/pages/ProfileEvaluationSchedule.vue
@@ -57,7 +57,7 @@ const props = defineProps({
const evaluations = createListResource({
doctype: 'LMS Certificate Request',
filters: {
- evaluator: user.data?.name,
+ evaluator: props.profile.data?.name,
status: ['!=', 'Cancelled'],
},
fields: [
diff --git a/frontend/src/pages/ProfileEvaluator.vue b/frontend/src/pages/ProfileEvaluator.vue
index a9637993..df9af8ba 100644
--- a/frontend/src/pages/ProfileEvaluator.vue
+++ b/frontend/src/pages/ProfileEvaluator.vue
@@ -43,18 +43,22 @@
:options="days"
v-model="slot.day"
@focusout.stop="update(slot.name, 'day', slot.day)"
+ :disabled="!isSessionUser()"
/>
@@ -69,20 +73,23 @@
:options="days"
v-model="newSlot.day"
@focusout.stop="add()"
+ :disabled="!isSessionUser()"
/>
-
-
+
{{ __('My calendar') }}
@@ -157,11 +166,19 @@ const props = defineProps({
})
onMounted(() => {
- if (user.data?.name !== props.profile.data?.name) {
+ if (user.data?.name !== props.profile.data?.name && !hasHigherAccess()) {
window.location.href = `/user/${props.profile.data?.username}`
}
})
+const hasHigherAccess = () => {
+ return user.data?.is_evaluator || user.data?.is_moderator
+}
+
+const isSessionUser = () => {
+ return user.data?.email === props.profile.data?.name
+}
+
const showSlotsTemplate = ref(0)
const from = ref(null)
const to = ref(null)
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 @@