Merge branch 'develop' of https://github.com/frappe/lms into feature/coupons

This commit is contained in:
Jannat Patel
2025-10-27 12:39:10 +05:30
57 changed files with 5300 additions and 4940 deletions

View File

@@ -62,7 +62,7 @@
<Tooltip :text="__('Enrolled Students')">
<span class="flex items-center">
<Users class="h-4 w-4 stroke-1.5 mr-1" />
{{ course.enrollments }}
{{ formatAmount(course.enrollments) }}
</span>
</Tooltip>
</div>
@@ -116,27 +116,30 @@
<CourseInstructors :instructors="course.instructors" />
</div>
<div v-if="course.paid_course" class="font-semibold">
{{ course.price }}
</div>
<div class="flex items-center space-x-2">
<div v-if="course.paid_course" class="font-semibold">
{{ course.price }}
</div>
<Tooltip
v-if="course.paid_certificate || course.enable_certification"
:text="__('Get Certified')"
>
<GraduationCap class="size-5 stroke-1.5 text-ink-gray-7" />
</Tooltip>
<Tooltip
v-if="course.paid_certificate || course.enable_certification"
:text="__('Get Certified')"
>
<GraduationCap class="size-5 stroke-1.5 text-ink-gray-7" />
</Tooltip>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { Award, BookOpen, GraduationCap, Star, Users } from 'lucide-vue-next'
import UserAvatar from '@/components/UserAvatar.vue'
import { sessionStore } from '@/stores/session'
import { Tooltip } from 'frappe-ui'
import { theme } from '@/utils/theme'
import { formatAmount } from '@/utils'
import CourseInstructors from '@/components/CourseInstructors.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import ProgressBar from '@/components/ProgressBar.vue'
const { user } = sessionStore()

View File

@@ -1,11 +1,12 @@
<template>
<div class="">
<div class="text-ink-gray-7">
<span v-if="instructors?.length == 1">
<router-link
:to="{
name: 'Profile',
params: { username: instructors[0].username },
}"
class="text-ink-gray-7 hover:text-ink-gray-9"
>
{{ instructors[0].full_name }}
</router-link>
@@ -16,6 +17,7 @@
name: 'Profile',
params: { username: instructors[0].username },
}"
class="text-ink-gray-7 hover:text-ink-gray-9"
>
{{ instructors[0].first_name }}
</router-link>
@@ -25,6 +27,7 @@
name: 'Profile',
params: { username: instructors[1].username },
}"
class="text-ink-gray-7 hover:text-ink-gray-9"
>
{{ instructors[1].first_name }}
</router-link>
@@ -35,6 +38,7 @@
name: 'Profile',
params: { username: instructors[0].username },
}"
class="text-ink-gray-7 hover:text-ink-gray-9"
>
{{ instructors[0].first_name }}
</router-link>

View File

@@ -3,7 +3,7 @@
class="flex flex-col border rounded-md p-3 h-full hover:border-outline-gray-3"
>
<div class="flex space-x-4 mb-4">
<div class="flex flex-col space-y-2 flex-1">
<div class="flex flex-col space-y-2 flex-1 break-all">
<div class="text-lg font-semibold text-ink-gray-9">
{{ job.company_name }}
</div>

View File

@@ -165,6 +165,14 @@ const addAssignments = () => {
})
}
const addProgrammingExercises = () => {
otherLinks.value.push({
label: 'Programming Exercises',
icon: 'Code',
to: 'ProgrammingExercises',
})
}
const addPrograms = async () => {
let canAddProgram = await checkIfCanAddProgram()
if (!canAddProgram) return

View File

@@ -50,7 +50,7 @@
<FileText class="h-5 w-5 stroke-1.5 text-ink-gray-7" />
</div>
<div class="flex flex-col">
<span>
<span class="text-ink-gray-9">
{{ chapter.scorm_package.file_name }}
</span>
<span class="text-sm text-ink-gray-4 mt-1">

View File

@@ -18,7 +18,7 @@
>
<template #body-content>
<div class="flex flex-col gap-4">
<p>
<p class="text-ink-gray-9">
{{
__(
'Submit your resume to proceed with your application for this position. Upon submission, it will be shared with the job poster.'
@@ -51,7 +51,7 @@
<FileText class="h-5 w-5 stroke-1.5 text-ink-gray-7" />
</div>
<div class="flex flex-col">
<span>
<span class="text-ink-gray-9">
{{ resume.file_name }}
</span>
<span class="text-sm text-ink-gray-4 mt-1">

View File

@@ -126,7 +126,7 @@ import {
Button,
toast,
} from 'frappe-ui'
import { computed, watch, reactive, ref, inject } from 'vue'
import { watch, reactive, ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { useOnboarding } from 'frappe-ui/frappe'
@@ -141,6 +141,7 @@ const existingQuestion = reactive({
question: '',
marks: 1,
})
const question = reactive({
question: '',
type: 'Choices',

View File

@@ -1,13 +1,13 @@
<template>
<div class="text-base border rounded-md w-1/3 mx-auto my-32">
<div class="border-b px-5 py-3 font-medium">
<div class="border-b px-5 py-3 font-medium text-ink-gray-9">
<span
class="inline-flex items-center before:bg-surface-red-5 before:w-2 before:h-2 before:rounded-md before:mr-2"
></span>
{{ __(title) }}
</div>
<div class="px-5 py-3">
<div class="mb-4 leading-6">
<div class="mb-4 leading-6 text-ink-gray-7">
{{ __(text) }}
</div>
<Button variant="solid" class="w-full" @click="redirect()">

View File

@@ -1,5 +1,5 @@
<template>
<div class="text-lg font-semibold mb-4">
<div class="text-lg font-semibold mb-4 text-ink-gray-9">
{{ __('My Notes') }}
</div>
<TextEditor

View File

@@ -69,9 +69,12 @@ const update = () => {
let imageFields = ['favicon', 'banner_image']
props.fields.forEach((f) => {
if (imageFields.includes(f.name)) {
fieldsToSave[f.name] = f.value ? f.value.file_url : null
fieldsToSave[f.name] =
branding.data[f.name] && branding.data[f.name].file_url
? branding.data[f.name].file_url
: null
} else {
fieldsToSave[f.name] = f.value
fieldsToSave[f.name] = branding.data[f.name]
}
})

View File

@@ -2,7 +2,9 @@
<Dialog v-model="show" :options="{ size: '5xl' }">
<template #body>
<div class="flex h-[calc(100vh_-_8rem)]">
<div class="flex w-52 shrink-0 flex-col bg-surface-gray-2 p-2">
<div
class="flex w-52 shrink-0 flex-col bg-surface-gray-2 p-2 overflow-y-auto"
>
<h1 class="mb-3 px-2 pt-2 text-lg font-semibold text-ink-gray-9">
{{ __('Settings') }}
</h1>

View File

@@ -20,7 +20,7 @@
<div class="text-ink-gray-5">
{{ __('Payment for ') }} {{ type }}:
</div>
<div class="leading-5">
<div class="leading-5 text-ink-gray-9">
{{ orderSummary.data.title }}
</div>
</div>
@@ -31,7 +31,7 @@
<div class="text-ink-gray-5">
{{ __('Original Amount') }}
</div>
<div class="">
<div class="text-ink-gray-9">
{{ orderSummary.data.original_amount_formatted }}
</div>
</div>
@@ -49,17 +49,17 @@
<div class="text-ink-gray-5">
{{ __('GST Amount') }}
</div>
<div>
<div class="text-ink-gray-9">
{{ orderSummary.data.gst_amount_formatted }}
</div>
</div>
<div
class="flex items-center justify-between border-t border-outline-gray-3 pt-4 mt-2"
>
<div class="text-lg font-semibold">
<div class="text-lg font-semibold text-ink-gray-9">
{{ __('Total') }}
</div>
<div class="text-lg font-semibold">
<div class="text-lg font-semibold text-ink-gray-9">
{{ orderSummary.data.total_amount_formatted }}
</div>
</div>
@@ -96,7 +96,7 @@
<div class="flex-1 lg:mr-10">
<div class="mb-5">
<div class="text-lg font-semibold">
<div class="text-lg font-semibold text-ink-gray-9">
{{ __('Address') }}
</div>
</div>

View File

@@ -144,7 +144,12 @@ watch(
)
watch(course, () => {
if (!isInstructor() && !course.data?.published && !course.data?.upcoming) {
if (
!isInstructor() &&
!user.data?.is_moderator &&
!course.data?.published &&
!course.data?.upcoming
) {
router.push({
name: 'Courses',
})

View File

@@ -21,7 +21,7 @@
</header>
<div class="mt-5 mb-5">
<div class="px-5 md:px-10 pb-5 mb-5 space-y-5 border-b">
<div class="text-lg font-semibold mb-4">
<div class="text-lg font-semibold mb-4 text-ink-gray-9">
{{ __('Details') }}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
@@ -138,7 +138,7 @@
</div>
<div class="px-5 md:px-10 pb-5 mb-5 space-y-5 border-b">
<div class="text-lg font-semibold">
<div class="text-lg font-semibold text-ink-gray-9">
{{ __('Settings') }}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
@@ -178,7 +178,7 @@
</div>
<div class="px-5 md:px-10 pb-5 mb-5 space-y-5 border-b">
<div class="text-lg font-semibold">
<div class="text-lg font-semibold text-ink-gray-9">
{{ __('About the Course') }}
</div>
<FormControl
@@ -234,7 +234,7 @@
</div>
<div class="px-5 md:px-10 pb-5 space-y-5 border-b">
<div class="text-lg font-semibold mt-5">
<div class="text-lg font-semibold mt-5 text-ink-gray-9">
{{ __('Pricing and Certification') }}
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
@@ -260,12 +260,14 @@
v-if="course.paid_course || course.paid_certificate"
v-model="course.course_price"
:label="__('Amount')"
:required="course.paid_course || course.paid_certificate"
/>
<Link
v-if="course.paid_certificate"
doctype="Course Evaluator"
v-model="course.evaluator"
:label="__('Evaluator')"
:required="course.paid_certificate"
:onCreate="
(value, close) => openSettings('Evaluators', close)
"
@@ -278,11 +280,13 @@
v-model="course.currency"
:filters="{ enabled: 1 }"
:label="__('Currency')"
:required="course.paid_course || course.paid_certificate"
/>
<FormControl
v-if="course.paid_certificate"
v-model="course.timezone"
:label="__('Timezone')"
:required="course.paid_certificate"
:placeholder="__('e.g. IST, UTC, GMT...')"
/>
</div>
@@ -290,7 +294,7 @@
</div>
<div class="px-5 md:px-10 pb-5 space-y-5">
<div class="text-lg font-semibold mt-5">
<div class="text-lg font-semibold mt-5 text-ink-gray-9">
{{ __('Meta Tags') }}
</div>
<div class="space-y-5">
@@ -325,7 +329,6 @@
<script setup>
import {
Breadcrumbs,
call,
TextEditor,
Button,
createResource,

View File

@@ -2,7 +2,7 @@
<div>
<div v-if="createdCourses.data?.length" class="mt-10">
<div class="flex items-center justify-between mb-3">
<span class="font-semibold text-lg">
<span class="font-semibold text-lg text-ink-gray-9">
{{ __('Courses Created') }}
</span>
<router-link

View File

@@ -5,27 +5,28 @@
<Breadcrumbs :items="[{ label: __('Home'), route: { name: 'Home' } }]" />
</header> -->
<div class="w-full px-5 pt-5 pb-10">
<div class="flex items-center justify-between">
<div class="space-y-2">
<div class="space-y-2">
<div class="flex items-center justify-between">
<div class="text-xl font-bold text-ink-gray-9">
{{ __('Hey') }}, {{ user.data?.full_name }} 👋
</div>
<div class="text-lg text-ink-gray-6">
{{ subtitle }}
<div>
<TabButtons v-if="isAdmin" v-model="currentTab" :buttons="tabs" />
<div
v-else
@click="showStreakModal = true"
class="bg-surface-amber-2 px-2 py-1 rounded-md cursor-pointer"
>
<span> 🔥 </span>
<span class="text-ink-gray-9">
{{ streakInfo.data?.current_streak }}
</span>
</div>
</div>
</div>
<div>
<TabButtons v-if="isAdmin" v-model="currentTab" :buttons="tabs" />
<div
v-else
@click="showStreakModal = true"
class="bg-surface-amber-2 px-2 py-1 rounded-md cursor-pointer"
>
<span> 🔥 </span>
<span>
{{ streakInfo.data?.current_streak }}
</span>
</div>
<div class="text-lg text-ink-gray-6 leading-6">
{{ subtitle }}
</div>
</div>

View File

@@ -20,7 +20,7 @@
}}
{{ __(' you are on a') }}
</div>
<div class="font-semibold text-xl">
<div class="font-semibold text-xl text-ink-gray-9">
{{ streakInfo.data?.current_streak }} {{ __('day streak') }}
</div>
</div>
@@ -33,7 +33,7 @@
<div class="text-ink-gray-6">
{{ __('Current Streak') }}
</div>
<div class="font-semibold text-lg">
<div class="font-semibold text-lg text-ink-gray-9">
{{ streakInfo.data?.current_streak }} {{ __('days') }}
</div>
</div>
@@ -41,7 +41,7 @@
<div class="text-ink-gray-6">
{{ __('Longest Streak') }}
</div>
<div class="font-semibold text-lg">
<div class="font-semibold text-lg text-ink-gray-9">
{{ streakInfo.data?.longest_streak }} {{ __('days') }}
</div>
</div>

View File

@@ -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
}

View File

@@ -142,7 +142,6 @@ const renderEditor = (holder) => {
return new EditorJS({
holder: holder,
tools: getEditorTools(true),
autofocus: true,
defaultBlock: 'markdown',
onChange: async (api, event) => {
enablePlyr()

View File

@@ -25,17 +25,17 @@
@click="openForm(program.name)"
class="border rounded-md p-3 hover:border-outline-gray-3 cursor-pointer space-y-2"
>
<div class="text-lg font-semibold">
<div class="text-lg font-semibold text-ink-gray-9">
{{ program.name }}
</div>
<div class="flex items-center space-x-1">
<div class="flex items-center space-x-1 text-ink-gray-7">
<BookOpen class="h-4 w-4 stroke-1.5 mr-1" />
<span>
{{ program.course_count }}
{{ program.course_count == 1 ? __('Course') : __('Courses') }}
</span>
</div>
<div class="flex items-center space-x-1">
<div class="flex items-center space-x-1 text-ink-gray-7">
<User class="h-4 w-4 stroke-1.5 mr-1" />
<span>
{{ program.member_count || 0 }}

View File

@@ -254,11 +254,7 @@ const props = defineProps({
const questions = ref([])
onMounted(() => {
if (
props.quizID == 'new' &&
!user.data?.is_moderator &&
!user.data?.is_instructor
) {
if (!user.data?.is_moderator && !user.data?.is_instructor) {
router.push({ name: 'Courses' })
}
if (props.quizID !== 'new') {