diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 3e28b0a51..8d6f60f0a 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -31,12 +31,35 @@ describe("Course Creation", () => { .contains("Preview Video") .type("https://www.youtube.com/embed/-LPmw2Znl2c"); cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}"); - cy.get(".search-input").click().type("frappe"); - cy.wait(1000); + cy.get("label") + .contains("Category") + .parent() + .within(() => { + cy.get("button").click(); + }); cy.get("[id^=headlessui-combobox-option-") .should("be.visible") .first() .click(); + + /* Instructor */ + cy.get("label") + .contains("Instructors") + .parent() + .within(() => { + cy.get("input").click().type("frappe"); + cy.get("input") + .invoke("attr", "aria-controls") + .as("instructor_list_id"); + }); + cy.get("@instructor_list_id").then((instructor_list_id) => { + cy.get(`[id^=${instructor_list_id}`) + .should("be.visible") + .within(() => { + cy.get("[id^=headlessui-combobox-option-").first().click(); + }); + }); + cy.get("label").contains("Published").click(); cy.get("label").contains("Published On").type("2021-01-01"); cy.button("Save").click(); diff --git a/frontend/package.json b/frontend/package.json index 5cf5af513..e5c1e94c5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@editorjs/paragraph": "^2.11.3", "@editorjs/simple-image": "^1.6.0", "chart.js": "^4.4.1", + "codemirror-editor-vue3": "^2.8.0", "dayjs": "^1.11.6", "feather-icons": "^4.28.0", "frappe-ui": "^0.1.69", diff --git a/frontend/src/components/Categories.vue b/frontend/src/components/Categories.vue new file mode 100644 index 000000000..20f9561e4 --- /dev/null +++ b/frontend/src/components/Categories.vue @@ -0,0 +1,151 @@ + + + diff --git a/frontend/src/components/Modals/EvaluationModal.vue b/frontend/src/components/Modals/EvaluationModal.vue index b103a4f27..201e4d94b 100644 --- a/frontend/src/components/Modals/EvaluationModal.vue +++ b/frontend/src/components/Modals/EvaluationModal.vue @@ -131,10 +131,16 @@ function submitEvaluation(close) { }, onError(err) { let message = err.messages?.[0] || err - let unavailabilityMessage = message.includes('unavailable') + let unavailabilityMessage + + if (typeof message === 'string') { + unavailabilityMessage = message?.includes('unavailable') + } else { + unavailabilityMessage = false + } createToast({ - title: unavailabilityMessage ? 'Evaluator is Unavailable' : 'Error', + title: unavailabilityMessage ? __('Evaluator is Unavailable') : '', text: message, icon: unavailabilityMessage ? 'alert-circle' : 'x', iconClasses: 'bg-yellow-600 text-white rounded-md p-px', diff --git a/frontend/src/components/Modals/ExplanationVideos.vue b/frontend/src/components/Modals/ExplanationVideos.vue index 35c0cfb39..2dbf920c0 100644 --- a/frontend/src/components/Modals/ExplanationVideos.vue +++ b/frontend/src/components/Modals/ExplanationVideos.vue @@ -27,8 +27,8 @@ const props = defineProps({ }) const file = computed(() => { - if (props.type == 'youtube') return '/Youtube.mp4' - if (props.type == 'quiz') return '/Quiz.mp4' - if (props.type == 'upload') return '/Upload.mp4' + if (props.type == 'youtube') return '/assets/lms/frontend/Youtube.mp4' + if (props.type == 'quiz') return '/assets/lms/frontend/Quiz.mp4' + if (props.type == 'upload') return '/assets/lms/frontend/Upload.mp4' }) diff --git a/frontend/src/components/Modals/Settings.vue b/frontend/src/components/Modals/Settings.vue index 77be067d7..843c945c9 100644 --- a/frontend/src/components/Modals/Settings.vue +++ b/frontend/src/components/Modals/Settings.vue @@ -6,7 +6,7 @@

{{ __('Settings') }}

-
+
+ import { Dialog, createDocumentResource } from 'frappe-ui' import { ref, computed, watch } from 'vue' +import { useSettings } from '@/stores/settings' import SettingDetails from '../SettingDetails.vue' import SidebarLink from '@/components/SidebarLink.vue' import Members from '@/components/Members.vue' +import Categories from '@/components/Categories.vue' const show = defineModel() const doctype = ref('LMS Settings') const activeTab = ref(null) +const settingsStore = useSettings() const data = createDocumentResource({ doctype: doctype.value, @@ -69,8 +79,8 @@ const data = createDocumentResource({ auto: true, }) -const tabs = computed(() => { - let _tabs = [ +const tabsStructure = computed(() => { + return [ { label: 'Settings', hideLabel: true, @@ -80,6 +90,23 @@ const tabs = computed(() => { description: 'Manage the members of your learning system', icon: 'UserRoundPlus', }, + ], + }, + { + label: 'Settings', + hideLabel: true, + items: [ + { + label: 'Categories', + description: 'Manage the members of your learning system', + icon: 'Network', + }, + ], + }, + { + label: 'Settings', + hideLabel: true, + items: [ { label: 'Payment Gateway', icon: 'DollarSign', @@ -125,8 +152,8 @@ const tabs = computed(() => { ], }, { - label: 'Settings', - hideLabel: true, + label: 'Customise', + hideLabel: false, items: [ { label: 'Sidebar', @@ -168,12 +195,6 @@ const tabs = computed(() => { }, ], }, - ], - }, - { - label: 'Settings', - hideLabel: true, - items: [ { label: 'Email Templates', icon: 'MailPlus', @@ -199,56 +220,19 @@ const tabs = computed(() => { }, ], }, - ], - }, - { - label: 'Settings', - hideLabel: true, - items: [ { label: 'Signup', icon: 'LogIn', - description: - 'Customize the signup page to inform users about your terms and policies', fields: [ { - label: 'Show terms of use on signup', - name: 'terms_of_use', - type: 'checkbox', - }, - { - label: 'Terms of Use Page', - name: 'terms_page', - type: 'Link', - doctype: 'Web Page', + label: 'Custom Content', + name: 'custom_signup_content', + type: 'Code', + mode: 'htmlmixed', + rows: 10, }, { - label: 'Show privacy policy on signup', - name: 'privacy_policy', - type: 'checkbox', - }, - { - label: 'Privacy Policy Page', - name: 'privacy_policy_page', - type: 'Link', - doctype: 'Web Page', - }, - { - type: 'Column Break', - }, - { - label: 'Show cookie policy on signup', - name: 'cookie_policy', - type: 'checkbox', - }, - { - label: 'Cookie Policy Page', - name: 'cookie_policy_page', - type: 'Link', - doctype: 'Web Page', - }, - { - label: 'Ask user category during signup', + label: 'Ask user category', name: 'user_category', type: 'checkbox', }, @@ -257,23 +241,28 @@ const tabs = computed(() => { ], }, ] +}) - return _tabs.map((tab) => { - tab.items = tab.items.filter((item) => { - if (item.condition) { - return item.condition() - } - return true - }) - return tab +const tabs = computed(() => { + return tabsStructure.value.map((tab) => { + return { + ...tab, + items: tab.items.filter((item) => { + return !item.condition || item.condition() + }), + } }) }) -watch(show, () => { +watch(show, async () => { if (show.value) { - activeTab.value = tabs.value[0].items[0] + const currentTab = await tabs.value + .flatMap((tab) => tab.items) + .find((item) => item.label === settingsStore.activeTab) + activeTab.value = currentTab || tabs.value[0].items[0] } else { activeTab.value = null + settingsStore.isSettingsOpen = false } }) diff --git a/frontend/src/components/SettingDetails.vue b/frontend/src/components/SettingDetails.vue index 550ad1fd9..e54b4b0d0 100644 --- a/frontend/src/components/SettingDetails.vue +++ b/frontend/src/components/SettingDetails.vue @@ -8,9 +8,15 @@ {{ __(description) }}
-
+
-
+
+ + +
@@ -41,6 +60,9 @@ import { FormControl, Button } from 'frappe-ui' import { computed } from 'vue' import Link from '@/components/Controls/Link.vue' +import Codemirror from 'codemirror-editor-vue3' +import 'codemirror/theme/seti.css' +import 'codemirror/mode/htmlmixed/htmlmixed.js' const props = defineProps({ fields: { @@ -94,3 +116,13 @@ const update = () => { props.data.save.submit() } + diff --git a/frontend/src/components/UserDropdown.vue b/frontend/src/components/UserDropdown.vue index 348e79181..5071513d6 100644 --- a/frontend/src/components/UserDropdown.vue +++ b/frontend/src/components/UserDropdown.vue @@ -67,25 +67,20 @@ import LMSLogo from '@/components/Icons/LMSLogo.vue' import { sessionStore } from '@/stores/session' import { Dropdown } from 'frappe-ui' import Apps from '@/components/Apps.vue' -import { - ChevronDown, - LogIn, - LogOut, - User, - ArrowRightLeft, - Settings, -} from 'lucide-vue-next' +import { ChevronDown, LogIn, LogOut, User, Settings } from 'lucide-vue-next' import { useRouter } from 'vue-router' import { convertToTitleCase } from '../utils' import { usersStore } from '@/stores/user' -import { ref, markRaw } from 'vue' +import { useSettings } from '@/stores/settings' +import { markRaw, watch, ref } from 'vue' import SettingsModal from '@/components/Modals/Settings.vue' const router = useRouter() -const showSettingsModal = ref(false) const { logout, branding } = sessionStore() let { userResource } = usersStore() +const settingsStore = useSettings() let { isLoggedIn } = sessionStore() +const showSettingsModal = ref(false) const props = defineProps({ isCollapsed: { @@ -94,6 +89,13 @@ const props = defineProps({ }, }) +watch( + () => settingsStore.isSettingsOpen, + (value) => { + showSettingsModal.value = value + } +) + const userDropdownOptions = [ { icon: User, @@ -118,7 +120,7 @@ const userDropdownOptions = [ icon: Settings, label: 'Settings', onClick: () => { - showSettingsModal.value = true + settingsStore.isSettingsOpen = true }, condition: () => { return userResource.data?.is_moderator diff --git a/frontend/src/pages/Batches.vue b/frontend/src/pages/Batches.vue index 77e73c5d4..26d3892fa 100644 --- a/frontend/src/pages/Batches.vue +++ b/frontend/src/pages/Batches.vue @@ -8,12 +8,12 @@ :items="[{ label: __('Batches'), route: { name: 'Batches' } }]" />
-
+