From 78a9eac356a5b2326ca15b26396fdd7f13855952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20G=C3=B3mez=20Soto?= Date: Tue, 31 Mar 2026 08:53:26 +0200 Subject: [PATCH 01/20] ci(build): add payments repository to APPS_JSON --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58f9e185..144202ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: - name: Set Branch run: | - export APPS_JSON='[{"url": "https://github.com/frappe/lms","branch": "main"}]' + export APPS_JSON='[{"url": "https://github.com/frappe/payments","branch": "version-15"},{"url": "https://github.com/frappe/lms","branch": "main"}]' echo "APPS_JSON_BASE64=$(echo $APPS_JSON | base64 -w 0)" >> $GITHUB_ENV echo "FRAPPE_BRANCH=version-15" >> $GITHUB_ENV @@ -61,4 +61,4 @@ jobs: ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} build-args: | "FRAPPE_BRANCH=${{ env.FRAPPE_BRANCH }}" - "APPS_JSON_BASE64=${{ env.APPS_JSON_BASE64 }}" \ No newline at end of file + "APPS_JSON_BASE64=${{ env.APPS_JSON_BASE64 }}" From 94a80603b0a0b8af8d9598130c464164ee4948b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20G=C3=B3mez?= <114139521+iamrubeng@users.noreply.github.com> Date: Wed, 1 Apr 2026 08:57:14 +0200 Subject: [PATCH 02/20] ci(build): update Frappe branch to version-16 in workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 144202ae..916d3cf6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: run: | export APPS_JSON='[{"url": "https://github.com/frappe/payments","branch": "version-15"},{"url": "https://github.com/frappe/lms","branch": "main"}]' echo "APPS_JSON_BASE64=$(echo $APPS_JSON | base64 -w 0)" >> $GITHUB_ENV - echo "FRAPPE_BRANCH=version-15" >> $GITHUB_ENV + echo "FRAPPE_BRANCH=version-16" >> $GITHUB_ENV - name: Set Image Tag run: | From 540c676206819e9529ebb1b435e381196af0d236 Mon Sep 17 00:00:00 2001 From: frappe-pr-bot Date: Fri, 3 Apr 2026 16:11:34 +0000 Subject: [PATCH 03/20] chore: update POT file --- lms/locale/main.pot | 285 ++++++++++++++++++++++++++++---------------- 1 file changed, 184 insertions(+), 101 deletions(-) diff --git a/lms/locale/main.pot b/lms/locale/main.pot index 3f687f57..cbf02c88 100644 --- a/lms/locale/main.pot +++ b/lms/locale/main.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Learning VERSION\n" "Report-Msgid-Bugs-To: jannat@frappe.io\n" -"POT-Creation-Date: 2026-03-27 16:17+0000\n" -"PO-Revision-Date: 2026-03-27 16:17+0000\n" +"POT-Creation-Date: 2026-04-03 16:11+0000\n" +"PO-Revision-Date: 2026-04-03 16:11+0000\n" "Last-Translator: jannat@frappe.io\n" "Language-Team: jannat@frappe.io\n" "MIME-Version: 1.0\n" @@ -334,7 +334,7 @@ msgstr "" msgid "All Batches" msgstr "" -#: frontend/src/pages/Courses/Courses.vue:54 lms/lms/widgets/BreadCrumb.html:3 +#: frontend/src/pages/Courses/Courses.vue:36 lms/lms/widgets/BreadCrumb.html:3 msgid "All Courses" msgstr "" @@ -926,11 +926,11 @@ msgstr "" msgid "Batch end date cannot be before the batch start date" msgstr "" -#: lms/lms/api.py:194 +#: lms/lms/api.py:193 msgid "Batch has already started." msgstr "" -#: lms/lms/api.py:189 +#: lms/lms/api.py:188 msgid "Batch is sold out." msgstr "" @@ -1040,7 +1040,7 @@ msgstr "" msgid "Cancelled" msgstr "" -#: lms/lms/api.py:2325 +#: lms/lms/api.py:2335 msgid "Cannot search for roles: {0}" msgstr "" @@ -1063,7 +1063,7 @@ msgstr "" #: frontend/src/pages/Batches/components/NewBatchModal.vue:51 #: frontend/src/pages/CertifiedParticipants.vue:38 #: frontend/src/pages/Courses/CourseForm.vue:23 -#: frontend/src/pages/Courses/Courses.vue:74 +#: frontend/src/pages/Courses/Courses.vue:56 #: frontend/src/pages/Courses/NewCourseModal.vue:21 #: lms/lms/doctype/lms_batch/lms_batch.json #: lms/lms/doctype/lms_category/lms_category.json @@ -1079,7 +1079,7 @@ msgstr "" msgid "Category added successfully" msgstr "" -#: frontend/src/utils/index.js:835 +#: frontend/src/utils/index.js:837 msgid "Category created successfully" msgstr "" @@ -1140,7 +1140,7 @@ msgstr "" #: frontend/src/pages/Batches/Batches.vue:85 #: frontend/src/pages/Courses/CourseCertification.vue:10 #: frontend/src/pages/Courses/CourseCertification.vue:135 -#: frontend/src/pages/Courses/Courses.vue:84 lms/fixtures/custom_field.json +#: frontend/src/pages/Courses/Courses.vue:66 lms/fixtures/custom_field.json #: lms/lms/doctype/certification/certification.json #: lms/lms/doctype/lms_batch/lms_batch.json #: lms/lms/doctype/lms_enrollment/lms_enrollment.json @@ -1667,6 +1667,10 @@ msgstr "" msgid "Correct Answer" msgstr "" +#: lms/lms/course_import_export.py:196 +msgid "Could not create the course ZIP file. Please try again later. Error: {0}" +msgstr "" + #. Label of the country (Link) field in DocType 'User' #. Label of the country (Link) field in DocType 'Job Opportunity' #. Label of the country (Link) field in DocType 'Payment Country' @@ -1941,9 +1945,9 @@ msgstr "" #: frontend/src/pages/Batches/components/BatchDashboard.vue:20 #: frontend/src/pages/Batches/components/BatchOverlay.vue:45 #: frontend/src/pages/Courses/CourseCertification.vue:127 -#: frontend/src/pages/Courses/CourseDetail.vue:143 -#: frontend/src/pages/Courses/Courses.vue:356 -#: frontend/src/pages/Courses/Courses.vue:363 frontend/src/pages/Lesson.vue:564 +#: frontend/src/pages/Courses/CourseDetail.vue:227 +#: frontend/src/pages/Courses/Courses.vue:374 +#: frontend/src/pages/Courses/Courses.vue:381 frontend/src/pages/Lesson.vue:564 #: frontend/src/pages/LessonForm.vue:475 #: frontend/src/pages/Programs/ProgramForm.vue:49 #: frontend/src/pages/Programs/Programs.vue:35 @@ -1984,7 +1988,7 @@ msgstr "" #: frontend/src/components/Modals/ChapterModal.vue:9 #: frontend/src/pages/Assignments.vue:19 #: frontend/src/pages/Batches/Batches.vue:33 -#: frontend/src/pages/Courses/Courses.vue:36 +#: frontend/src/pages/Courses/Courses.vue:18 #: frontend/src/pages/ProgrammingExercises/ProgrammingExercises.vue:33 #: frontend/src/pages/Quizzes.vue:10 msgid "Create" @@ -2061,7 +2065,7 @@ msgid "Create your first quiz" msgstr "" #: frontend/src/pages/Assignments.vue:175 -#: frontend/src/pages/Courses/Courses.vue:346 +#: frontend/src/pages/Courses/Courses.vue:335 msgid "Created" msgstr "" @@ -2129,7 +2133,7 @@ msgid "Cyan" msgstr "" #. Label of the show_dashboard (Check) field in DocType 'LMS Settings' -#: frontend/src/pages/Courses/CourseDetail.vue:102 +#: frontend/src/pages/Courses/CourseDetail.vue:116 #: lms/lms/doctype/lms_settings/lms_settings.json msgid "Dashboard" msgstr "" @@ -2204,6 +2208,7 @@ msgstr "" #: frontend/src/components/Settings/Badges.vue:171 #: frontend/src/components/Settings/Coupons/CouponList.vue:133 #: frontend/src/pages/Batches/BatchForm.vue:507 +#: frontend/src/pages/Courses/CourseDetail.vue:216 #: frontend/src/pages/Courses/CourseForm.vue:544 #: frontend/src/pages/ProgrammingExercises/ProgrammingExerciseForm.vue:71 #: frontend/src/pages/ProgrammingExercises/ProgrammingExercises.vue:240 @@ -2256,7 +2261,7 @@ msgstr "" msgid "Deleting this lesson will permanently remove it from the course. This action cannot be undone. Are you sure you want to continue?" msgstr "" -#: lms/lms/api.py:757 +#: lms/lms/api.py:756 msgid "Deletion not allowed for {0}" msgstr "" @@ -2307,6 +2312,10 @@ msgstr "" msgid "Details cannot be empty." msgstr "" +#: frontend/src/pages/Courses/CourseImportModal.vue:33 +msgid "Device" +msgstr "" + #. Label of the disable_pwa (Check) field in DocType 'LMS Settings' #: lms/lms/doctype/lms_settings/lms_settings.json msgid "Disable PWA" @@ -2392,6 +2401,10 @@ msgstr "" msgid "Don’t miss this opportunity to enhance your skills. Click below to complete your enrollment" msgstr "" +#: frontend/src/pages/Courses/CourseImportModal.vue:28 +msgid "Drag and drop a ZIP file, or upload from your" +msgstr "" + #. Label of the dream_companies (Data) field in DocType 'User' #: lms/fixtures/custom_field.json msgid "Dream Companies" @@ -2666,7 +2679,7 @@ msgstr "" #: frontend/src/pages/Batches/Batches.vue:328 #: frontend/src/pages/Batches/components/AdminBatchDashboard.vue:5 #: frontend/src/pages/Courses/CourseDashboard.vue:5 -#: frontend/src/pages/Courses/Courses.vue:349 +#: frontend/src/pages/Courses/Courses.vue:338 #: frontend/src/pages/Programs/StudentPrograms.vue:96 msgid "Enrolled" msgstr "" @@ -2781,6 +2794,10 @@ msgstr "" msgid "Error deleting payment gateways" msgstr "" +#: lms/lms/course_import_export.py:298 +msgid "Error downloading file" +msgstr "" + #: frontend/src/components/Settings/GoogleMeetAccountModal.vue:194 msgid "Error updating Google Meet Account" msgstr "" @@ -2875,7 +2892,7 @@ msgstr "" msgid "Evaluator added successfully" msgstr "" -#: frontend/src/components/Settings/Evaluators.vue:190 +#: frontend/src/components/Settings/Evaluators.vue:192 msgid "Evaluator deleted successfully" msgstr "" @@ -2970,6 +2987,10 @@ msgstr "" msgid "Explore More" msgstr "" +#: frontend/src/pages/Courses/CourseDetail.vue:209 +msgid "Export" +msgstr "" + #. Option for the 'Status' (Select) field in DocType 'LMS Assignment #. Submission' #. Option for the 'Status' (Select) field in DocType 'LMS Certificate @@ -3021,7 +3042,7 @@ msgstr "" msgid "Failed to update badge assignment: " msgstr "" -#: frontend/src/utils/index.js:899 +#: frontend/src/utils/index.js:901 msgid "Failed to update meta tags {0}" msgstr "" @@ -3059,6 +3080,15 @@ msgstr "" msgid "File Type" msgstr "" +#: lms/lms/course_import_export.py:286 +msgid "File not found" +msgstr "" + +#: frontend/src/pages/Courses/CourseImportModal.vue:150 +#: frontend/src/pages/Courses/CourseImportModal.vue:166 +msgid "File upload failed. Please try again." +msgstr "" + #: frontend/src/components/AssessmentPlugin.vue:55 msgid "Filter assignments by course" msgstr "" @@ -3543,6 +3573,7 @@ msgid "Image: Corrupted Data Stream" msgstr "" #: frontend/src/components/Sidebar/Configuration.vue:36 +#: frontend/src/pages/Courses/CourseImportModal.vue:83 msgid "Import" msgstr "" @@ -3550,8 +3581,16 @@ msgstr "" msgid "Import Batch" msgstr "" -#: frontend/src/pages/Courses/Courses.vue:20 -msgid "Import Course" +#: frontend/src/pages/Courses/CourseImportModal.vue:5 +msgid "Import Course from ZIP" +msgstr "" + +#: frontend/src/pages/Courses/Courses.vue:353 +msgid "Import via Data Import Tool" +msgstr "" + +#: frontend/src/pages/Courses/Courses.vue:363 +msgid "Import via ZIP" msgstr "" #. Option for the 'Status' (Select) field in DocType 'LMS Certificate @@ -3680,10 +3719,26 @@ msgstr "" msgid "Invalid Quiz ID in content" msgstr "" -#: lms/lms/api.py:760 +#: lms/lms/course_import_export.py:770 +msgid "Invalid ZIP file" +msgstr "" + +#: lms/lms/course_import_export.py:339 +msgid "Invalid course ZIP: Missing course.json" +msgstr "" + +#: lms/lms/api.py:1010 +msgid "Invalid course or chapter name" +msgstr "" + +#: lms/lms/api.py:759 msgid "Invalid document name" msgstr "" +#: lms/lms/api.py:1017 +msgid "Invalid file path in package" +msgstr "" + #: frontend/src/components/Sidebar/AppSidebar.vue:515 msgid "Invite your team and students" msgstr "" @@ -4266,7 +4321,7 @@ msgstr "" msgid "LinkedIn ID" msgstr "" -#: frontend/src/pages/Courses/Courses.vue:329 +#: frontend/src/pages/Courses/Courses.vue:318 msgid "Live" msgstr "" @@ -4291,7 +4346,7 @@ msgstr "" #: frontend/src/pages/Batches/components/AdminBatchDashboard.vue:119 #: frontend/src/pages/CertifiedParticipants.vue:118 #: frontend/src/pages/Courses/CourseDashboard.vue:119 -#: frontend/src/pages/Courses/Courses.vue:107 +#: frontend/src/pages/Courses/Courses.vue:89 #: frontend/src/pages/JobApplications.vue:101 #: frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmissions.vue:133 #: frontend/src/pages/ProgrammingExercises/ProgrammingExercises.vue:123 @@ -4397,7 +4452,7 @@ msgstr "" msgid "Mark" msgstr "" -#: frontend/src/pages/Notifications.vue:12 +#: frontend/src/pages/Notifications.vue:14 msgid "Mark all as read" msgstr "" @@ -4732,7 +4787,7 @@ msgstr "" msgid "Meta Tags" msgstr "" -#: lms/lms/api.py:1542 +#: lms/lms/api.py:1552 msgid "Meta tags should be a list." msgstr "" @@ -4792,11 +4847,11 @@ msgstr "" msgid "Modified By" msgstr "" -#: lms/lms/api.py:171 +#: lms/lms/api.py:170 msgid "Module Name is incorrect or does not exist." msgstr "" -#: lms/lms/api.py:167 +#: lms/lms/api.py:166 msgid "Module is incorrect." msgstr "" @@ -4861,7 +4916,7 @@ msgstr "" #: frontend/src/components/Settings/Members.vue:17 #: frontend/src/components/Settings/PaymentGateways.vue:16 #: frontend/src/components/Settings/ZoomSettings.vue:17 -#: frontend/src/pages/Courses/Courses.vue:333 +#: frontend/src/pages/Courses/Courses.vue:322 #: frontend/src/pages/Programs/Programs.vue:10 #: lms/lms/doctype/lms_badge/lms_badge.json msgid "New" @@ -4869,7 +4924,7 @@ msgstr "" #: frontend/src/pages/Batches/Batches.vue:10 #: frontend/src/pages/Batches/components/NewBatchModal.vue:5 -#: frontend/src/pages/Notifications.vue:90 lms/www/_lms.py:154 +#: frontend/src/pages/Notifications.vue:95 lms/www/_lms.py:154 msgid "New Batch" msgstr "" @@ -4877,9 +4932,9 @@ msgstr "" msgid "New Coupon" msgstr "" -#: frontend/src/pages/Courses/Courses.vue:13 +#: frontend/src/pages/Courses/Courses.vue:346 #: frontend/src/pages/Courses/NewCourseModal.vue:5 -#: frontend/src/pages/Notifications.vue:89 lms/www/_lms.py:98 +#: frontend/src/pages/Notifications.vue:94 lms/www/_lms.py:98 msgid "New Course" msgstr "" @@ -4924,11 +4979,11 @@ msgstr "" msgid "New Zoom Account" msgstr "" -#: lms/lms/utils.py:427 +#: lms/lms/utils.py:471 msgid "New comment in batch {0}" msgstr "" -#: lms/lms/utils.py:418 +#: lms/lms/utils.py:462 msgid "New reply on the topic {0} in course {1}" msgstr "" @@ -5045,6 +5100,10 @@ msgstr "" msgid "No quizzes added yet." msgstr "" +#: frontend/src/pages/Notifications.vue:153 +msgid "No read notifications" +msgstr "" + #: frontend/src/components/Controls/Autocomplete.vue:136 #: frontend/src/components/Controls/MultiSelect.vue:77 #: frontend/src/pages/Search/Search.vue:47 @@ -5067,6 +5126,10 @@ msgstr "" msgid "No submissions" msgstr "" +#: frontend/src/pages/Notifications.vue:152 +msgid "No unread notifications" +msgstr "" + #: frontend/src/components/EmptyState.vue:5 lms/templates/course_list.html:13 msgid "No {0}" msgstr "" @@ -5122,10 +5185,6 @@ msgstr "" msgid "Notes" msgstr "" -#: frontend/src/pages/Notifications.vue:143 -msgid "Nothing to see here." -msgstr "" - #. Label of the notification_sent (Check) field in DocType 'LMS Batch' #. Label of the notification_sent (Check) field in DocType 'LMS Course' #: lms/lms/doctype/lms_batch/lms_batch.json @@ -5140,6 +5199,10 @@ msgstr "" msgid "Notifications" msgstr "" +#: frontend/src/pages/Notifications.vue:160 +msgid "Notifications you have read will appear here." +msgstr "" + #: lms/lms/widgets/NoPreviewModal.html:30 msgid "Notify me when available" msgstr "" @@ -5197,6 +5260,10 @@ msgstr "" msgid "Only PDF files are allowed." msgstr "" +#: frontend/src/utils/index.js:661 +msgid "Only ZIP files are allowed." +msgstr "" + #: frontend/src/utils/index.js:658 msgid "Only document file of type .doc or .docx are allowed." msgstr "" @@ -5213,7 +5280,7 @@ msgstr "" msgid "Only show batches that offer a certificate" msgstr "" -#: frontend/src/pages/Courses/Courses.vue:80 +#: frontend/src/pages/Courses/Courses.vue:62 msgid "Only show courses that offer a certificate" msgstr "" @@ -5221,7 +5288,7 @@ msgstr "" msgid "Only zip files are allowed" msgstr "" -#: frontend/src/utils/index.js:664 +#: frontend/src/utils/index.js:666 msgid "Only {0} file is allowed." msgstr "" @@ -5339,7 +5406,7 @@ msgstr "" msgid "Oversee all users, content, and system settings" msgstr "" -#: frontend/src/pages/Courses/CourseDetail.vue:97 +#: frontend/src/pages/Courses/CourseDetail.vue:111 msgid "Overview" msgstr "" @@ -5594,11 +5661,11 @@ msgstr "" msgid "Please add {1} for {3} to send calendar invites for evaluations." msgstr "" -#: lms/lms/user.py:75 +#: lms/lms/user.py:77 msgid "Please ask your administrator to verify your sign-up" msgstr "" -#: lms/lms/user.py:73 +#: lms/lms/user.py:75 msgid "Please check your email for verification" msgstr "" @@ -5678,15 +5745,15 @@ msgstr "" msgid "Please login to access the quiz." msgstr "" -#: lms/lms/api.py:163 +#: lms/lms/api.py:162 msgid "Please login to continue with payment." msgstr "" -#: lms/lms/utils.py:2038 +#: lms/lms/utils.py:2081 msgid "Please login to view program details." msgstr "" -#: lms/lms/utils.py:2003 +#: lms/lms/utils.py:2046 msgid "Please login to view programs." msgstr "" @@ -6455,7 +6522,7 @@ msgstr "" msgid "SEO" msgstr "" -#: frontend/src/utils/index.js:681 +#: frontend/src/utils/index.js:683 msgid "SVG contains potentially unsafe content." msgstr "" @@ -6486,7 +6553,7 @@ msgstr "" #: frontend/src/components/Settings/Transactions/TransactionDetails.vue:29 #: frontend/src/pages/Batches/BatchDetail.vue:17 #: frontend/src/pages/Batches/components/NewBatchModal.vue:107 -#: frontend/src/pages/Courses/CourseDetail.vue:17 +#: frontend/src/pages/Courses/CourseDetail.vue:21 #: frontend/src/pages/Courses/NewCourseModal.vue:69 #: frontend/src/pages/JobForm.vue:12 frontend/src/pages/LessonForm.vue:14 #: frontend/src/pages/ProgrammingExercises/ProgrammingExerciseForm.vue:107 @@ -6533,7 +6600,7 @@ msgstr "" #: frontend/src/components/Settings/Evaluators.vue:57 #: frontend/src/components/Settings/Members.vue:25 -#: frontend/src/pages/Courses/Courses.vue:64 frontend/src/pages/Jobs.vue:59 +#: frontend/src/pages/Courses/Courses.vue:46 frontend/src/pages/Jobs.vue:59 #: frontend/src/pages/Search/Search.vue:5 #: frontend/src/pages/Search/Search.vue:250 msgid "Search" @@ -6716,7 +6783,7 @@ msgstr "" #: frontend/src/components/Settings/Settings.vue:9 #: frontend/src/components/Sidebar/AppSidebar.vue:640 -#: frontend/src/pages/Courses/CourseDetail.vue:107 +#: frontend/src/pages/Courses/CourseDetail.vue:121 #: frontend/src/pages/ProfileRoles.vue:4 #: frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue:19 #: frontend/src/pages/QuizForm.vue:86 @@ -7101,7 +7168,7 @@ msgstr "" msgid "Sunday" msgstr "" -#: lms/lms/api.py:1033 +#: lms/lms/api.py:1043 msgid "Suspicious pattern found in {0}: {1}" msgstr "" @@ -7189,7 +7256,7 @@ msgstr "" msgid "Template" msgstr "" -#: lms/lms/user.py:40 +#: lms/lms/user.py:42 msgid "Temporarily Disabled" msgstr "" @@ -7237,7 +7304,7 @@ msgstr "" msgid "The Google Meet account does not have a Google Calendar configured. Please set up a Google Calendar first." msgstr "" -#: lms/lms/utils.py:2274 +#: lms/lms/utils.py:2317 msgid "The batch does not exist." msgstr "" @@ -7245,7 +7312,7 @@ msgstr "" msgid "The batch you have enrolled for is starting tomorrow. Please be prepared and be on time for the session." msgstr "" -#: lms/lms/utils.py:1757 +#: lms/lms/utils.py:1800 msgid "The coupon code '{0}' is invalid." msgstr "" @@ -7269,7 +7336,7 @@ msgstr "" msgid "The last day to schedule your evaluations is " msgstr "" -#: lms/lms/utils.py:2258 +#: lms/lms/utils.py:2301 msgid "The lesson does not exist." msgstr "" @@ -7285,7 +7352,7 @@ msgstr "" msgid "The slot is already booked by another participant." msgstr "" -#: lms/lms/utils.py:1455 lms/lms/utils.py:1955 +#: lms/lms/utils.py:1498 lms/lms/utils.py:1998 msgid "The specified batch does not exist." msgstr "" @@ -7332,6 +7399,10 @@ msgstr "" msgid "This badge has not been assigned to any students yet" msgstr "" +#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:56 +msgid "This batch is not associated with this course." +msgstr "" + #. Label of the expire (Check) field in DocType 'Certification' #: lms/lms/doctype/certification/certification.json msgid "This certificate does no expire" @@ -7343,15 +7414,15 @@ msgstr "" msgid "This class has ended" msgstr "" -#: lms/lms/utils.py:1786 +#: lms/lms/utils.py:1829 msgid "This coupon has expired." msgstr "" -#: lms/lms/utils.py:1789 +#: lms/lms/utils.py:1832 msgid "This coupon has reached its maximum usage limit." msgstr "" -#: lms/lms/utils.py:1798 +#: lms/lms/utils.py:1841 msgid "This coupon is not applicable to this {0}." msgstr "" @@ -7359,7 +7430,7 @@ msgstr "" msgid "This course has:" msgstr "" -#: lms/lms/utils.py:1717 +#: lms/lms/utils.py:1760 msgid "This course is free." msgstr "" @@ -7566,11 +7637,11 @@ msgstr "" msgid "To Date" msgstr "" -#: lms/lms/utils.py:1731 +#: lms/lms/utils.py:1774 msgid "To join this batch, please contact the Administrator." msgstr "" -#: lms/lms/user.py:41 +#: lms/lms/user.py:43 msgid "Too many users signed up recently, so the registration is disabled. Please try back in an hour" msgstr "" @@ -7690,7 +7761,7 @@ msgstr "" msgid "Unable to add member" msgstr "" -#: frontend/src/utils/index.js:840 +#: frontend/src/utils/index.js:842 msgid "Unable to create category" msgstr "" @@ -7718,10 +7789,14 @@ msgid "Under Review" msgstr "" #: frontend/src/pages/Batches/Batches.vue:326 -#: frontend/src/pages/Courses/Courses.vue:347 +#: frontend/src/pages/Courses/Courses.vue:336 msgid "Unpublished" msgstr "" +#: lms/lms/course_import_export.py:773 +msgid "Unsafe file path detected" +msgstr "" + #: frontend/src/components/Modals/EditCoverImage.vue:60 #: frontend/src/components/UnsplashImageBrowser.vue:54 msgid "Unsplash" @@ -7741,7 +7816,7 @@ msgstr "" #. Label of the upcoming (Check) field in DocType 'LMS Course' #: frontend/src/pages/Batches/Batches.vue:324 #: frontend/src/pages/Courses/CourseForm.vue:129 -#: frontend/src/pages/Courses/Courses.vue:337 +#: frontend/src/pages/Courses/Courses.vue:326 #: lms/lms/doctype/lms_certificate_request/lms_certificate_request.json #: lms/lms/doctype/lms_course/lms_course.json msgid "Upcoming" @@ -7849,7 +7924,7 @@ msgstr "" msgid "User Skill" msgstr "" -#: lms/lms/api.py:1783 +#: lms/lms/api.py:1793 msgid "User does not have permission to access this user's profile details." msgstr "" @@ -8053,39 +8128,39 @@ msgstr "" msgid "You are already certified for this course. Click on the card below to open your certificate." msgstr "" -#: lms/lms/api.py:183 +#: lms/lms/api.py:182 msgid "You are already enrolled for this batch." msgstr "" -#: lms/lms/api.py:177 +#: lms/lms/api.py:176 msgid "You are already enrolled for this course." msgstr "" -#: lms/lms/utils.py:1251 +#: lms/lms/utils.py:1294 msgid "You are not authorized to view the assessments of this batch." msgstr "" -#: lms/lms/utils.py:1453 +#: lms/lms/utils.py:1496 msgid "You are not authorized to view the chart data of this batch." msgstr "" -#: lms/lms/utils.py:2045 +#: lms/lms/utils.py:2088 msgid "You are not authorized to view the details of this program." msgstr "" -#: lms/lms/utils.py:1663 +#: lms/lms/utils.py:1706 msgid "You are not authorized to view the discussion replies for this topic." msgstr "" -#: lms/lms/utils.py:1616 +#: lms/lms/utils.py:1659 msgid "You are not authorized to view the discussion topics for this item." msgstr "" -#: lms/lms/utils.py:1217 +#: lms/lms/utils.py:1260 msgid "You are not authorized to view the question details." msgstr "" -#: lms/lms/utils.py:1362 +#: lms/lms/utils.py:1405 msgid "You are not authorized to view the students of this batch." msgstr "" @@ -8130,11 +8205,11 @@ msgstr "" msgid "You cannot change the roles in read-only mode." msgstr "" -#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:59 +#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:64 msgid "You cannot enroll in an unpublished course." msgstr "" -#: lms/lms/utils.py:2106 +#: lms/lms/utils.py:2149 msgid "You cannot enroll in an unpublished program." msgstr "" @@ -8150,35 +8225,35 @@ msgstr "" msgid "You cannot schedule evaluations for past slots." msgstr "" -#: lms/lms/utils.py:2286 +#: lms/lms/utils.py:2329 msgid "You do not have access to this batch." msgstr "" -#: lms/lms/utils.py:2269 +#: lms/lms/utils.py:2312 msgid "You do not have access to this course." msgstr "" -#: lms/lms/api.py:855 lms/lms/doctype/lms_batch/lms_batch.py:365 +#: lms/lms/api.py:854 lms/lms/doctype/lms_batch/lms_batch.py:365 msgid "You do not have permission to access announcements for this batch." msgstr "" -#: lms/lms/api.py:2282 +#: lms/lms/api.py:2292 msgid "You do not have permission to access badges." msgstr "" -#: lms/lms/api.py:1145 +#: lms/lms/api.py:1155 msgid "You do not have permission to access heatmap data." msgstr "" -#: lms/lms/api.py:2121 +#: lms/lms/api.py:2131 msgid "You do not have permission to access lesson completion stats." msgstr "" -#: lms/lms/api.py:2161 +#: lms/lms/api.py:2171 msgid "You do not have permission to access this course's assessment data." msgstr "" -#: lms/lms/api.py:1684 +#: lms/lms/api.py:1694 msgid "You do not have permission to access this course's progress data." msgstr "" @@ -8186,7 +8261,7 @@ msgstr "" msgid "You do not have permission to access this page." msgstr "" -#: lms/lms/api.py:1362 lms/lms/api.py:1371 +#: lms/lms/api.py:1372 lms/lms/api.py:1381 msgid "You do not have permission to cancel this evaluation." msgstr "" @@ -8194,31 +8269,35 @@ msgstr "" msgid "You do not have permission to create a live class." msgstr "" -#: lms/lms/api.py:925 +#: lms/lms/api.py:924 msgid "You do not have permission to delete this batch." msgstr "" -#: lms/lms/api.py:1099 +#: lms/lms/api.py:1109 msgid "You do not have permission to delete this chapter." msgstr "" -#: lms/lms/api.py:885 +#: lms/lms/api.py:884 msgid "You do not have permission to delete this course." msgstr "" -#: lms/lms/api.py:514 +#: lms/lms/api.py:513 msgid "You do not have permission to delete this lesson." msgstr "" -#: lms/lms/api.py:597 lms/lms/api.py:973 +#: lms/lms/api.py:2372 +msgid "You do not have permission to export this course." +msgstr "" + +#: lms/lms/api.py:596 lms/lms/api.py:972 msgid "You do not have permission to modify this chapter." msgstr "" -#: lms/lms/api.py:534 +#: lms/lms/api.py:533 msgid "You do not have permission to modify this lesson." msgstr "" -#: lms/lms/api.py:1431 +#: lms/lms/api.py:1441 msgid "You do not have permission to modify this role." msgstr "" @@ -8230,11 +8309,11 @@ msgstr "" msgid "You do not have permission to set up calendar events for this evaluation." msgstr "" -#: lms/lms/api.py:1567 lms/lms/api.py:1571 +#: lms/lms/api.py:1577 lms/lms/api.py:1581 msgid "You do not have permission to update meta tags." msgstr "" -#: lms/lms/api.py:1608 +#: lms/lms/api.py:1618 msgid "You do not have permission to update this submission." msgstr "" @@ -8259,7 +8338,7 @@ msgstr "" msgid "You have already exceeded the maximum number of attempts allowed for this quiz." msgstr "" -#: lms/lms/api.py:207 +#: lms/lms/api.py:206 msgid "You have already purchased the certificate for this course." msgstr "" @@ -8323,7 +8402,7 @@ msgstr "" msgid "You must be enrolled in the course to submit a review" msgstr "" -#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:73 +#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:78 msgid "You need to complete the payment for this course before enrolling." msgstr "" @@ -8344,6 +8423,10 @@ msgstr "" msgid "You will have to get {0}% correct answers in order to pass the quiz." msgstr "" +#: frontend/src/pages/Notifications.vue:159 +msgid "You're all caught up! Check back later for updates." +msgstr "" + #: lms/templates/emails/mentor_request_creation_email.html:4 msgid "You've applied to become a mentor for this course. Your request is currently under review." msgstr "" @@ -8666,7 +8749,7 @@ msgstr "" msgid "{0} Quizzes" msgstr "" -#: lms/lms/api.py:776 lms/lms/api.py:784 +#: lms/lms/api.py:775 lms/lms/api.py:783 msgid "{0} Settings not found" msgstr "" @@ -8710,7 +8793,7 @@ msgstr "" msgid "{0} is your evaluator" msgstr "" -#: lms/lms/utils.py:520 +#: lms/lms/utils.py:564 msgid "{0} mentioned you in a comment" msgstr "" @@ -8718,11 +8801,11 @@ msgstr "" msgid "{0} mentioned you in a comment in your batch." msgstr "" -#: lms/lms/utils.py:473 lms/lms/utils.py:479 +#: lms/lms/utils.py:517 lms/lms/utils.py:523 msgid "{0} mentioned you in a comment in {1}" msgstr "" -#: lms/lms/api.py:838 +#: lms/lms/api.py:837 msgid "{0} not found" msgstr "" From 831f11939847900805caf16fbb83a40e172e8dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20G=C3=B3mez?= <114139521+iamrubeng@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:25:15 +0200 Subject: [PATCH 04/20] build(init): add payments dependency --- docker/init.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/init.sh b/docker/init.sh index ed74f101..edba3170 100644 --- a/docker/init.sh +++ b/docker/init.sh @@ -24,6 +24,7 @@ bench set-redis-socketio-host redis://redis:6379 sed -i '/redis/d' ./Procfile sed -i '/watch/d' ./Procfile +bench get-app payments bench get-app lms bench new-site lms.localhost \ @@ -32,6 +33,7 @@ bench new-site lms.localhost \ --admin-password admin \ --no-mariadb-socket +bench --site lms.localhost install-app payments bench --site lms.localhost install-app lms bench --site lms.localhost set-config developer_mode 1 bench --site lms.localhost clear-cache From 221ac4fad912654b3556eaa4eebf68c01f66f2c1 Mon Sep 17 00:00:00 2001 From: raizasafeel <89463672+raizasafeel@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:03:53 +0530 Subject: [PATCH 05/20] revert: change switches into checkbox --- .../Transactions/TransactionDetails.vue | 10 +++---- .../Settings/Transactions/TransactionList.vue | 26 +++++++++---------- frontend/src/pages/CertifiedParticipants.vue | 9 +++---- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/Settings/Transactions/TransactionDetails.vue b/frontend/src/components/Settings/Transactions/TransactionDetails.vue index 07c27539..74f29c72 100644 --- a/frontend/src/components/Settings/Transactions/TransactionDetails.vue +++ b/frontend/src/components/Settings/Transactions/TransactionDetails.vue @@ -32,16 +32,14 @@
- - - -
@@ -47,12 +45,12 @@ :rows="transactions.data" row-key="name" :options="{ - showTooltip: false, - selectable: false, - onRowClick: (row: { [key: string]: any }) => { - openForm(row) - }, - }" + showTooltip: false, + selectable: false, + onRowClick: (row: { [key: string]: any }) => { + openForm(row) + }, + }" >
- -
@@ -129,7 +129,6 @@ import { createListResource, FormControl, Select, - Switch, usePageMeta, } from 'frappe-ui' import { computed, inject, onMounted, ref } from 'vue' From 1d04f4fd917b62f5dd4442a541280fc40a4178b4 Mon Sep 17 00:00:00 2001 From: raizasafeel <89463672+raizasafeel@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:19:50 +0530 Subject: [PATCH 06/20] fix: allow zero amount checkout with coupons --- frontend/src/pages/Billing.vue | 29 ++++++++------------ lms/lms/payments.py | 50 +++++++++++++++++++++++++--------- lms/lms/utils.py | 20 ++++++++------ 3 files changed, 61 insertions(+), 38 deletions(-) diff --git a/frontend/src/pages/Billing.vue b/frontend/src/pages/Billing.vue index c8fe6b72..f2515b79 100644 --- a/frontend/src/pages/Billing.vue +++ b/frontend/src/pages/Billing.vue @@ -199,8 +199,15 @@ }} - @@ -326,16 +333,10 @@ const paymentLink = createResource({ let data = { doctype: props.type == 'batch' ? 'LMS Batch' : 'LMS Course', docname: props.name, - title: orderSummary.data.title, - amount: orderSummary.data.original_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, + country: billingDetails.country, } return data }, @@ -458,14 +459,8 @@ const changeCurrency = (country) => { orderSummary.reload() } -const redirectTo = computed(() => { - if (props.type == 'course') { - return getLmsRoute(`courses/${props.name}`) - } else if (props.type == 'batch') { - return getLmsRoute(`batches/${props.name}`) - } else if (props.type == 'certificate') { - return getLmsRoute(`courses/${props.name}/certification`) - } +const isZeroAmount = computed(() => { + return orderSummary.data && parseFloat(orderSummary.data.total_amount) <= 0 }) watch(billingDetails, () => { diff --git a/lms/lms/payments.py b/lms/lms/payments.py index 35aac722..caced7ca 100644 --- a/lms/lms/payments.py +++ b/lms/lms/payments.py @@ -1,5 +1,11 @@ import frappe +from lms.lms.utils import ( + complete_enrollment, + get_lms_route, + get_order_summary, +) + def get_payment_gateway(): return frappe.db.get_single_value("LMS Settings", "payment_gateway") @@ -21,22 +27,25 @@ def validate_currency(payment_gateway, currency): def get_payment_link( doctype: str, docname: str, - title: str, - amount: float, - discount_amount: float, - gst_amount: float, - currency: str, address: dict, - redirect_to: str, payment_for_certificate: int, - coupon_code: str = None, - coupon: str = None, + coupon_code: str, + country: str, ): payment_gateway = get_payment_gateway() address = frappe._dict(address) - original_amount = amount - amount -= discount_amount + redirect_to = get_redirect_url(doctype, docname, payment_for_certificate) + + details = frappe._dict(get_order_summary(doctype, docname, coupon=coupon_code, country=country)) + title = details.title + currency = details.currency + original_amount = details.original_amount + discount_amount = details.get("discount_amount", 0) + gst_amount = details.get("gst_applied", 0) + amount = original_amount - discount_amount amount_with_gst = get_amount_with_gst(amount, gst_amount) + coupon = details.get("coupon") + total_amount = amount_with_gst if amount_with_gst else amount payment = record_payment( address, @@ -51,10 +60,16 @@ def get_payment_link( coupon_code, coupon, ) + + if total_amount <= 0: + frappe.db.set_value("LMS Payment", payment.name, "payment_received", 1) + complete_enrollment(payment.name, doctype, docname) + return redirect_to + controller = get_controller(payment_gateway) payment_details = { - "amount": amount_with_gst if amount_with_gst else amount, + "amount": total_amount, "title": f"Payment for {doctype} {title} {docname}", "description": f"{address.billing_name}'s payment for {title}", "reference_doctype": doctype, @@ -99,8 +114,8 @@ def record_payment( amount_with_gst: float = 0, discount_amount: float = 0, payment_for_certificate: int = 0, - coupon_code: str = None, - coupon: str = None, + coupon_code: str | None = None, + coupon: str | None = None, ): address = frappe._dict(address) address_name = save_address(address) @@ -138,6 +153,15 @@ def record_payment( return payment_doc +def get_redirect_url(doctype: str, docname: str, payment_for_certificate: int) -> str: + if int(payment_for_certificate): + return get_lms_route(f"courses/{docname}/certification") + elif doctype == "LMS Course": + return get_lms_route(f"courses/{docname}") + else: + return get_lms_route(f"batches/{docname}") + + def save_address(address: dict) -> str: filters = {"email_id": frappe.session.user} exists = frappe.db.exists("Address", filters) diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 7dbaddb8..134cade9 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1864,17 +1864,21 @@ def update_payment_record(doctype: str, docname: str): if len(request): data = request[0].data data = frappe._dict(json.loads(data)) - payment_doc = get_payment_doc(data.payment) update_payment_details(data) - update_coupon_redemption(payment_doc) + complete_enrollment(data.payment, doctype, docname) - if payment_doc.payment_for_certificate: - update_certificate_purchase(docname, data.payment) - elif doctype == "LMS Course": - enroll_in_course(docname, data.payment) - else: - enroll_in_batch(docname, data.payment) + +def complete_enrollment(payment_name: str, doctype: str, docname: str): + payment_doc = get_payment_doc(payment_name) + update_coupon_redemption(payment_doc) + + if payment_doc.payment_for_certificate: + update_certificate_purchase(docname, payment_name) + elif doctype == "LMS Course": + enroll_in_course(docname, payment_name) + else: + enroll_in_batch(docname, payment_name) def get_integration_requests(doctype: str, docname: str): From 93161b827885f9be44b353956cc9b3c9c80e0401 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 6 Apr 2026 16:19:52 +0530 Subject: [PATCH 07/20] fix: sidebar links in mobile --- frontend/src/components/MobileLayout.vue | 109 +++++++++++------------ frontend/src/utils/index.js | 24 ++--- 2 files changed, 64 insertions(+), 69 deletions(-) diff --git a/frontend/src/components/MobileLayout.vue b/frontend/src/components/MobileLayout.vue index 8aa5a5dc..1015b7c8 100644 --- a/frontend/src/components/MobileLayout.vue +++ b/frontend/src/components/MobileLayout.vue @@ -57,7 +57,7 @@ import { getSidebarLinks } from '@/utils' import { useRouter } from 'vue-router' import { call } from 'frappe-ui' -import { watch, ref, onMounted } from 'vue' +import { ref, watch } from 'vue' import { sessionStore } from '@/stores/session' import { useSettings } from '@/stores/settings' import { usersStore } from '@/stores/user' @@ -68,26 +68,13 @@ let { isLoggedIn } = sessionStore() const { sidebarSettings } = useSettings() const router = useRouter() let { userResource } = usersStore() -const sidebarLinks = ref(getSidebarLinks()) +const sidebarLinks = ref([]) const otherLinks = ref([]) const showMenu = ref(false) const menu = ref(null) const isModerator = ref(false) const isInstructor = ref(false) -onMounted(() => { - sidebarSettings.reload( - {}, - { - onSuccess(data) { - destructureSidebarLinks() - filterLinksToShow(data) - addOtherLinks() - }, - } - ) -}) - const handleOutsideClick = (e) => { if (menu.value && !menu.value.contains(e.target)) { showMenu.value = false @@ -126,65 +113,57 @@ const filterLinksToShow = (data) => { const addOtherLinks = () => { if (user) { - otherLinks.value.push({ - label: 'Notifications', - icon: 'Bell', - to: 'Notifications', - }) - otherLinks.value.push({ - label: 'Profile', - icon: 'UserRound', - }) - otherLinks.value.push({ - label: 'Log out', - icon: 'LogOut', - }) + addLink('Notifications', 'Bell', 'Notifications') + addLink('Profile', 'UserRound') + addLink('Log out', 'LogOut') } else { - otherLinks.value.push({ - label: 'Log in', - icon: 'LogIn', - }) + addLink('Log in', 'LogIn') } } -watch(userResource, () => { - if (userResource.data) { - isModerator.value = userResource.data.is_moderator - isInstructor.value = userResource.data.is_instructor - addPrograms() - if (isModerator.value || isInstructor.value) { - addProgrammingExercises() - addQuizzes() - addAssignments() +const addLink = (label, icon, to = '') => { + if (otherLinks.value.some((link) => link.label === label)) return + otherLinks.value.push({ + label: label, + icon: icon, + to: to, + }) +} + +const updateSidebarLinks = () => { + sidebarLinks.value = getSidebarLinks(true) + destructureSidebarLinks() + sidebarSettings.reload( + {}, + { + onSuccess: async (data) => { + filterLinksToShow(data) + await addPrograms() + if (isModerator.value || isInstructor.value) { + addQuizzes() + addAssignments() + addProgrammingExercises() + } + addOtherLinks() + }, } - } -}) + ) +} const addQuizzes = () => { - otherLinks.value.push({ - label: 'Quizzes', - icon: 'CircleHelp', - to: 'Quizzes', - }) + addLink('Quizzes', 'CircleHelp', 'Quizzes') } const addAssignments = () => { - otherLinks.value.push({ - label: 'Assignments', - icon: 'Pencil', - to: 'Assignments', - }) + addLink('Assignments', 'Pencil', 'Assignments') } const addProgrammingExercises = () => { - otherLinks.value.push({ - label: 'Programming Exercises', - icon: 'Code', - to: 'ProgrammingExercises', - }) + addLink('Programming Exercises', 'Code', 'ProgrammingExercises') } const addPrograms = async () => { + if (sidebarLinks.value.some((link) => link.label === 'Programs')) return let canAddProgram = await checkIfCanAddProgram() if (!canAddProgram) return let activeFor = ['Programs', 'ProgramDetail'] @@ -198,7 +177,21 @@ const addPrograms = async () => { }) } +watch( + userResource, + async () => { + await userResource.promise + if (userResource.data) { + isModerator.value = userResource.data.is_moderator + isInstructor.value = userResource.data.is_instructor + } + updateSidebarLinks() + }, + { immediate: true } +) + const checkIfCanAddProgram = async () => { + if (!userResource.data) return false if (isModerator.value || isInstructor.value) { return true } diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 198d8c87..4be629cb 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -403,8 +403,8 @@ export function getUserTimezone() { } } -export function getSidebarLinks() { - let links = getSidebarItems() +export function getSidebarLinks(forMobile = false) { + let links = getSidebarItems(forMobile) links.forEach((link) => { link.items = link.items.filter((item) => { @@ -419,7 +419,7 @@ export function getSidebarLinks() { return links } -const getSidebarItems = () => { +const getSidebarItems = (forMobile = false) => { const { userResource } = usersStore() const { settings } = useSettings() @@ -441,7 +441,7 @@ const getSidebarItems = () => { icon: 'Search', to: 'Search', condition: () => { - return userResource?.data + return !forMobile && userResource?.data }, }, { @@ -449,7 +449,7 @@ const getSidebarItems = () => { icon: 'Bell', to: 'Notifications', condition: () => { - return userResource?.data + return !forMobile && userResource?.data }, }, ], @@ -476,7 +476,7 @@ const getSidebarItems = () => { activeFor: ['Programs', 'ProgramDetail'], await: true, condition: () => { - return checkIfCanAddProgram() + return checkIfCanAddProgram(forMobile) }, }, { @@ -514,7 +514,8 @@ const getSidebarItems = () => { : settings.data?.contact_us_email, condition: () => { return ( - (settings?.data?.contact_us_email && + (!forMobile && + settings?.data?.contact_us_email && userResource?.data) || settings?.data?.contact_us_url ) @@ -531,7 +532,7 @@ const getSidebarItems = () => { icon: 'CircleHelp', to: 'Quizzes', condition: () => { - return isAdmin() + return !forMobile && isAdmin() }, activeFor: [ 'Quizzes', @@ -546,7 +547,7 @@ const getSidebarItems = () => { icon: 'Pencil', to: 'Assignments', condition: () => { - return isAdmin() + return !forMobile && isAdmin() }, activeFor: [ 'Assignments', @@ -559,7 +560,7 @@ const getSidebarItems = () => { icon: 'Code', to: 'ProgrammingExercises', condition: () => { - return isAdmin() + return !forMobile && isAdmin() }, activeFor: [ 'ProgrammingExercises', @@ -581,10 +582,11 @@ const isAdmin = () => { ) } -const checkIfCanAddProgram = () => { +const checkIfCanAddProgram = (forMobile = false) => { const { userResource } = usersStore() const { programs } = useSettings() if (!userResource.data) return false + if (forMobile) return false if (userResource?.data?.is_moderator || userResource?.data?.is_instructor) { return true } From ab1bed8f305f974d1c834812964f1be665797c0b Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 6 Apr 2026 17:06:44 +0530 Subject: [PATCH 08/20] fix: course card gradient toggle when theme changed --- frontend/src/components/CourseCard.vue | 14 ++++++++------ frontend/src/components/Sidebar/UserDropdown.vue | 12 ++---------- frontend/src/utils/theme.ts | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 frontend/src/utils/theme.ts diff --git a/frontend/src/components/CourseCard.vue b/frontend/src/components/CourseCard.vue index 0e63c036..0c7d6b2d 100644 --- a/frontend/src/components/CourseCard.vue +++ b/frontend/src/components/CourseCard.vue @@ -1,7 +1,7 @@