Merge branch 'develop' of https://github.com/frappe/lms into certification-redesign

This commit is contained in:
Jannat Patel
2025-04-14 22:50:47 +05:30
28 changed files with 4296 additions and 3674 deletions

View File

@@ -38,7 +38,7 @@
<div class="mb-4"> <div class="mb-4">
<Button @click="openFileSelector" :loading="uploading"> <Button @click="openFileSelector" :loading="uploading">
{{ {{
uploading ? `Uploading ${progress}%` : 'Upload an zip file' uploading ? `Uploading ${progress}%` : 'Upload an ZIP file'
}} }}
</Button> </Button>
</div> </div>

View File

@@ -8,98 +8,109 @@
{{ __('Save') }} {{ __('Save') }}
</Button> </Button>
</header> </header>
<div class="w-1/2 mx-auto py-5"> <div class="w-3/4 mx-auto py-5">
<div class=""> <div class="">
<div class="text-lg font-semibold mb-4"> <div class="text-lg font-semibold mb-4">
{{ __('Details') }} {{ __('Details') }}
</div> </div>
<div class="space-y-4 mb-4"> <div class="space-y-10 mb-4">
<FormControl <div class="grid grid-cols-2 gap-10">
v-model="batch.title"
:label="__('Title')"
:required="true"
class="w-full"
/>
<div class="flex items-center space-x-5">
<FormControl <FormControl
v-model="batch.published" v-model="batch.title"
type="checkbox" :label="__('Title')"
:label="__('Published')" :required="true"
class="w-full"
/> />
<FormControl <MultiSelect
v-model="batch.allow_self_enrollment" v-model="instructors"
type="checkbox" doctype="User"
:label="__('Allow self enrollment')" :label="__('Instructors')"
/> :required="true"
<FormControl :filters="{ ignore_user_type: 1 }"
v-model="batch.certification"
type="checkbox"
:label="__('Certification')"
/> />
</div> </div>
</div>
</div> <div class="grid grid-cols-2 gap-10">
<div class="mb-4"> <div class="flex flex-col space-y-5">
<div class="text-xs text-ink-gray-5 mb-2"> <FormControl
{{ __('Meta Image') }} v-model="batch.published"
</div> type="checkbox"
<FileUploader :label="__('Published')"
v-if="!batch.image" />
:fileTypes="['image/*']" <FormControl
:validateFile="validateFile" v-model="batch.allow_self_enrollment"
@success="(file) => saveImage(file)" type="checkbox"
> :label="__('Allow self enrollment')"
<template v-slot="{ file, progress, uploading, openFileSelector }"> />
<div class="flex items-center"> <FormControl
<div class="border rounded-md w-fit py-5 px-20"> v-model="batch.certification"
<Image class="size-5 stroke-1 text-ink-gray-7" /> type="checkbox"
:label="__('Certification')"
/>
</div>
<div>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Meta Image') }}
</div> </div>
<div class="ml-4"> <FileUploader
<Button @click="openFileSelector"> v-if="!batch.image"
{{ __('Upload') }} :fileTypes="['image/*']"
</Button> :validateFile="validateFile"
<div class="mt-2 text-ink-gray-5 text-sm"> @success="(file) => saveImage(file)"
{{ >
__( <template
'Appears when the batch URL is shared on any online platform' v-slot="{ file, progress, uploading, openFileSelector }"
) >
}} <div class="flex items-center">
<div class="border rounded-md w-fit py-5 px-20">
<Image class="size-5 stroke-1 text-ink-gray-7" />
</div>
<div class="ml-4">
<Button @click="openFileSelector">
{{ __('Upload') }}
</Button>
<div class="mt-2 text-ink-gray-5 text-sm">
{{
__(
'Appears when the batch URL is shared on any online platform'
)
}}
</div>
</div>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img
:src="batch.image.file_url"
class="border rounded-md w-40"
/>
<div class="ml-4">
<Button @click="removeImage()">
{{ __('Remove') }}
</Button>
<div class="mt-2 text-ink-gray-5 text-sm">
{{
__(
'Appears when the batch URL is shared on any online platform'
)
}}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img :src="batch.image.file_url" class="border rounded-md w-40" />
<div class="ml-4">
<Button @click="removeImage()">
{{ __('Remove') }}
</Button>
<div class="mt-2 text-ink-gray-5 text-sm">
{{
__(
'Appears when the batch URL is shared on any online platform'
)
}}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<MultiSelect
v-model="instructors"
doctype="User"
:label="__('Instructors')"
:required="true"
:filters="{ ignore_user_type: 1 }"
/>
<div class="my-10"> <div class="my-10">
<div class="text-lg font-semibold mb-4"> <div class="text-lg font-semibold mb-4">
{{ __('Date and Time') }} {{ __('Date and Time') }}
</div> </div>
<div class="grid grid-cols-2 gap-10"> <div class="grid grid-cols-3 gap-10">
<div> <div>
<FormControl <FormControl
v-model="batch.start_date" v-model="batch.start_date"
@@ -115,14 +126,6 @@
class="mb-4" class="mb-4"
:required="true" :required="true"
/> />
<FormControl
v-model="batch.timezone"
:label="__('Timezone')"
type="text"
:placeholder="__('Example: IST (+5:30)')"
class="mb-4"
:required="true"
/>
</div> </div>
<div> <div>
<FormControl <FormControl
@@ -140,6 +143,16 @@
:required="true" :required="true"
/> />
</div> </div>
<div>
<FormControl
v-model="batch.timezone"
:label="__('Timezone')"
type="text"
:placeholder="__('Example: IST (+5:30)')"
class="mb-4"
:required="true"
/>
</div>
</div> </div>
</div> </div>
@@ -147,7 +160,7 @@
<div class="text-lg font-semibold mb-4"> <div class="text-lg font-semibold mb-4">
{{ __('Settings') }} {{ __('Settings') }}
</div> </div>
<div class="grid grid-cols-2 gap-10"> <div class="grid grid-cols-3 gap-10">
<div> <div>
<FormControl <FormControl
v-model="batch.seat_count" v-model="batch.seat_count"
@@ -162,11 +175,6 @@
type="date" type="date"
class="mb-4" class="mb-4"
/> />
<Link
doctype="Email Template"
:label="__('Email Template')"
v-model="batch.confirmation_email_template"
/>
</div> </div>
<div> <div>
<FormControl <FormControl
@@ -191,6 +199,13 @@
v-model="batch.category" v-model="batch.category"
/> />
</div> </div>
<div>
<Link
doctype="Email Template"
:label="__('Email Template')"
v-model="batch.confirmation_email_template"
/>
</div>
</div> </div>
</div> </div>
@@ -198,17 +213,16 @@
<div class="text-lg font-semibold mb-4"> <div class="text-lg font-semibold mb-4">
{{ __('Payment') }} {{ __('Payment') }}
</div> </div>
<div> <FormControl
<FormControl v-model="batch.paid_batch"
v-model="batch.paid_batch" type="checkbox"
type="checkbox" :label="__('Paid Batch')"
:label="__('Paid Batch')" />
/> <div class="grid grid-cols-3 gap-10 mt-4">
<FormControl <FormControl
v-model="batch.amount" v-model="batch.amount"
:label="__('Amount')" :label="__('Amount')"
type="number" type="number"
class="my-4"
/> />
<Link <Link
doctype="Currency" doctype="Currency"
@@ -445,7 +459,7 @@ const createNewBatch = () => {
}) })
}, },
onError(err) { onError(err) {
showToast('Error', err.messages?.[0] || err, 'x') showToast('Message', err.messages?.[0] || err, 'alert-circle')
}, },
} }
) )
@@ -464,7 +478,7 @@ const editBatchDetails = () => {
}) })
}, },
onError(err) { onError(err) {
showToast('Error', err.messages?.[0] || err, 'x') showToast('Message', err.messages?.[0] || err, 'alert-circle')
}, },
} }
) )

View File

@@ -50,8 +50,7 @@ export const sessionStore = defineStore('lms-session', () => {
brand.name = data.app_name brand.name = data.app_name
brand.logo = data.app_logo brand.logo = data.app_logo
brand.favicon = brand.favicon =
data.favicon?.file_url || data.favicon?.file_url || '/assets/lms/frontend/learning.svg'
'/assets/lms/frontend/public/learning.svg'
}, },
}) })

View File

@@ -109,7 +109,7 @@ export function showToast(title, text, icon, iconClasses = null) {
icon: icon, icon: icon,
iconClasses: iconClasses, iconClasses: iconClasses,
position: icon == 'check' ? 'bottom-right' : 'top-center', position: icon == 'check' ? 'bottom-right' : 'top-center',
timeout: 5, timeout: icon != 'check' ? 10 : 5,
}) })
} }

View File

@@ -246,7 +246,7 @@ on_login = "lms.lms.user.on_login"
add_to_apps_screen = [ add_to_apps_screen = [
{ {
"name": "lms", "name": "lms",
"logo": "/assets/lms/images/lms-logo.png", "logo": "/assets/lms/frontend/learning.svg",
"title": "Learning", "title": "Learning",
"route": "/lms", "route": "/lms",
"has_permission": "lms.lms.api.check_app_permission", "has_permission": "lms.lms.api.check_app_permission",

View File

@@ -53,7 +53,12 @@ class LMSBatch(Document):
if self.paid_batch: if self.paid_batch:
installed_apps = frappe.get_installed_apps() installed_apps = frappe.get_installed_apps()
if "payments" not in installed_apps: if "payments" not in installed_apps:
frappe.throw(_("Please install the Payments app to create a paid batches.")) documentation_link = "https://docs.frappe.io/learning/setting-up-payment-gateway"
frappe.throw(
_(
"Please install the Payments App to create a paid batch. Refer to the documentation for more details. {0}"
).format(documentation_link)
)
def validate_amount_and_currency(self): def validate_amount_and_currency(self):
if self.paid_batch and (not self.amount or not self.currency): if self.paid_batch and (not self.amount or not self.currency):

View File

@@ -50,7 +50,12 @@ class LMSCourse(Document):
if self.paid_course: if self.paid_course:
installed_apps = frappe.get_installed_apps() installed_apps = frappe.get_installed_apps()
if "payments" not in installed_apps: if "payments" not in installed_apps:
frappe.throw(_("Please install the Payments app to create a paid courses.")) documentation_link = "https://docs.frappe.io/learning/setting-up-payment-gateway"
frappe.throw(
_(
"Please install the Payments App to create a paid course. Refer to the documentation for more details. {0}"
).format(documentation_link)
)
def validate_certification(self): def validate_certification(self):
if self.enable_certification and self.paid_certificate: if self.enable_certification and self.paid_certificate:

View File

@@ -20,10 +20,11 @@ frappe.ui.form.on("LMS Settings", {
frm.get_field("payments_app_is_not_installed").html(` frm.get_field("payments_app_is_not_installed").html(`
<div class="alert alert-warning"> <div class="alert alert-warning">
Please install the Please install the
<a target="_blank" style="color: var(--alert-text-warning); background: var(--alert-bg-warning);" href="https://frappecloud.com/marketplace/apps/payments"> <a target="_blank" style="text-decoration: underline; color: var(--alert-text-warning); background: var(--alert-bg-warning);" href="https://frappecloud.com/marketplace/apps/payments">Payments app</a>
Payments app to enable payment gateway. Refer to the
</a> <a target="_blank" style="text-decoration: underline; color: var(--alert-text-warning); background: var(--alert-bg-warning);" href="https://docs.frappe.io/learning/setting-up-payment-gateway">Documentation</a>
to enable payment gateway. for more information.
</div>
`); `);
}, },
}); });

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -168,6 +168,11 @@ def get_meta_from_document(app_path):
["job_title", "company_logo", "description"], ["job_title", "company_logo", "description"],
as_dict=True, as_dict=True,
) )
if job_opening.description:
soup = BeautifulSoup(job_opening.description, "html.parser")
job_opening.description = soup.get_text()
return { return {
"title": job_opening.job_title, "title": job_opening.job_title,
"image": job_opening.company_logo, "image": job_opening.company_logo,