feat: onboarding

This commit is contained in:
Jannat Patel
2025-03-26 13:08:06 +05:30
parent f9b2471b32
commit aa979b96f2
23 changed files with 4602 additions and 2882 deletions

View File

@@ -190,11 +190,15 @@
</div>
</div>
</div>
<BulkCertificates v-model="openCertificateDialog" :batch="batch.data" />
<BulkCertificates
v-if="batch.data"
v-model="openCertificateDialog"
:batch="batch.data"
/>
</template>
<script setup>
import { computed, inject, ref } from 'vue'
import { useRouteQuery } from '@vueuse/router'
import { computed, inject, ref, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Breadcrumbs, Button, createResource, Tabs, Badge } from 'frappe-ui'
import CourseInstructors from '@/components/CourseInstructors.vue'
import UserAvatar from '@/components/UserAvatar.vue'
@@ -226,52 +230,10 @@ import BatchFeedback from '@/components/BatchFeedback.vue'
const user = inject('$user')
const showAnnouncementModal = ref(false)
const openCertificateDialog = ref(false)
const route = useRoute()
const router = useRouter()
const tabIndex = ref(0)
const props = defineProps({
batchName: {
type: String,
required: true,
},
})
const batch = createResource({
url: 'lms.lms.utils.get_batch_details',
cache: ['batch', props.batchName],
params: {
batch: props.batchName,
},
auto: true,
})
const breadcrumbs = computed(() => {
let crumbs = [{ label: 'Batches', route: { name: 'Batches' } }]
if (!isStudent.value) {
crumbs.push({
label: 'Details',
route: {
name: 'BatchDetail',
params: {
batchName: batch.data?.name,
},
},
})
}
crumbs.push({
label: batch?.data?.title,
route: { name: 'Batch', params: { batchName: props.batchName } },
})
return crumbs
})
const isStudent = computed(() => {
return (
user?.data &&
batch.data?.students?.length &&
batch.data?.students.includes(user.data.name)
)
})
const tabIndex = useRouteQuery('tab', 0, { transform: Number })
const tabs = computed(() => {
let batchTabs = []
batchTabs.push({
@@ -313,6 +275,61 @@ const tabs = computed(() => {
return batchTabs
})
const props = defineProps({
batchName: {
type: String,
required: true,
},
})
onMounted(() => {
const hash = route.hash
if (hash) {
tabs.value.forEach((tab, index) => {
if (tab.label?.toLowerCase() === hash.replace('#', '')) {
tabIndex.value = index
}
})
}
})
const batch = createResource({
url: 'lms.lms.utils.get_batch_details',
cache: ['batch', props.batchName],
params: {
batch: props.batchName,
},
auto: true,
})
const breadcrumbs = computed(() => {
let crumbs = [{ label: 'Batches', route: { name: 'Batches' } }]
if (!isStudent.value) {
crumbs.push({
label: 'Details',
route: {
name: 'BatchDetail',
params: {
batchName: batch.data?.name,
},
},
})
}
crumbs.push({
label: batch?.data?.title,
route: { name: 'Batch', params: { batchName: props.batchName } },
})
return crumbs
})
const isStudent = computed(() => {
return (
user?.data &&
batch.data?.students?.length &&
batch.data?.students.includes(user.data.name)
)
})
const redirectToLogin = () => {
window.location.href = `/login?redirect-to=/lms/batches/${props.batchName}`
}
@@ -321,6 +338,13 @@ const openAnnouncementModal = () => {
showAnnouncementModal.value = true
}
watch(tabIndex, () => {
const tab = tabs.value[tabIndex.value]
if (tab.label != route.hash.replace('#', '')) {
router.push({ ...route, hash: `#${tab.label.toLowerCase()}` })
}
})
const pageMeta = computed(() => {
return {
title: batch.data?.title,

View File

@@ -271,9 +271,11 @@ import { showToast } from '@/utils'
import { Image } from 'lucide-vue-next'
import { capture } from '@/telemetry'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
import { useOnboarding } from 'frappe-ui/frappe'
const router = useRouter()
const user = inject('$user')
const { updateOnboardingStep } = useOnboarding('learning')
const props = defineProps({
batchName: {
@@ -426,6 +428,9 @@ const createNewBatch = () => {
{
onSuccess(data) {
capture('batch_created')
updateOnboardingStep('create_first_batch', true, false, () => {
localStorage.setItem('firstBatch', data.name)
})
router.push({
name: 'BatchDetail',
params: {

View File

@@ -105,7 +105,6 @@ import {
Select,
TabButtons,
} from 'frappe-ui'
import { useRouteQuery } from '@vueuse/router'
import { computed, inject, onMounted, ref, watch } from 'vue'
import { BookOpen, Plus } from 'lucide-vue-next'
import { updateDocumentTitle } from '@/utils'
@@ -120,10 +119,8 @@ const currentCategory = ref(null)
const title = ref('')
const certification = ref(false)
const filters = ref({})
const currentTab = useRouteQuery(
'tab',
user.data?.is_student ? 'All' : 'Upcoming'
)
const is_student = computed(() => user.data?.is_student)
const currentTab = ref(is_student.value ? 'All' : 'Upcoming')
const orderBy = ref('start_date')
onMounted(() => {
@@ -208,12 +205,12 @@ const updateTabFilter = () => {
if (!user.data) {
return
}
if (currentTab.value == 'Enrolled' && user.data?.is_student) {
if (currentTab.value == 'Enrolled' && is_student.value) {
filters.value['enrolled'] = 1
delete filters.value['start_date']
delete filters.value['published']
orderBy.value = 'start_date desc'
} else if (user.data?.is_student) {
} else if (is_student.value) {
delete filters.value['enrolled']
} else {
delete filters.value['start_date']
@@ -232,7 +229,7 @@ const updateTabFilter = () => {
}
const updateStudentFilter = () => {
if (!user.data || (user.data?.is_student && currentTab.value != 'Enrolled')) {
if (!user.data || (is_student.value && currentTab.value != 'Enrolled')) {
filters.value['start_date'] = ['>=', dayjs().format('YYYY-MM-DD')]
filters.value['published'] = 1
}
@@ -285,12 +282,17 @@ const batchTabs = computed(() => {
label: __('All'),
},
]
if (user.data?.is_student) {
tabs.push({ label: __('Enrolled') })
} else {
if (
user.data?.is_moderator ||
user.data?.is_instructor ||
user.data?.is_evaluator
) {
tabs.push({ label: __('Upcoming') })
tabs.push({ label: __('Archived') })
tabs.push({ label: __('Unpublished') })
} else if (user.data) {
tabs.push({ label: __('Enrolled') })
}
return tabs
})

View File

@@ -270,6 +270,7 @@ import { useRouter } from 'vue-router'
import CourseOutline from '@/components/CourseOutline.vue'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
import { capture } from '@/telemetry'
import { useOnboarding } from 'frappe-ui/frappe'
import { useSettings } from '@/stores/settings'
const user = inject('$user')
@@ -278,6 +279,7 @@ const router = useRouter()
const instructors = ref([])
const settingsStore = useSettings()
const app = getCurrentInstance()
const { updateOnboardingStep } = useOnboarding('learning')
const { $dialog } = app.appContext.config.globalProperties
const props = defineProps({
@@ -443,9 +445,9 @@ const submitCourse = () => {
onSuccess(data) {
capture('course_created')
showToast('Success', 'Course created successfully', 'check')
/* if (!settingsStore.onboardingDetails.data?.is_onboarded) {
settingsStore.onboardingDetails.reload()
} */
updateOnboardingStep('create_first_course', true, false, () => {
localStorage.setItem('firstCourse', data.name)
})
router.push({
name: 'CourseForm',
params: { courseName: data.name },

View File

@@ -101,7 +101,6 @@ import {
Select,
TabButtons,
} from 'frappe-ui'
import { useRouteQuery } from '@vueuse/router'
import { computed, inject, onMounted, ref, watch } from 'vue'
import { BookOpen, Plus } from 'lucide-vue-next'
import { updateDocumentTitle } from '@/utils'
@@ -116,7 +115,7 @@ const currentCategory = ref(null)
const title = ref('')
const certification = ref(false)
const filters = ref({})
const currentTab = useRouteQuery('tab', 'Live')
const currentTab = ref('Live')
onMounted(() => {
setFiltersFromQuery()
@@ -285,10 +284,14 @@ const courseTabs = computed(() => {
label: __('Upcoming'),
},
]
if (user.data?.is_student) {
tabs.push({ label: __('Enrolled') })
} else if (user.data) {
if (
user.data?.is_moderator ||
user.data?.is_instructor ||
user.data?.is_evaluator
) {
tabs.push({ label: __('Created') })
} else if (user.data) {
tabs.push({ label: __('Enrolled') })
}
return tabs
})

View File

@@ -92,13 +92,13 @@ import LessonHelp from '@/components/LessonHelp.vue'
import { ChevronRight } from 'lucide-vue-next'
import { updateDocumentTitle, createToast, getEditorTools } from '@/utils'
import { capture } from '@/telemetry'
import { useSettings } from '@/stores/settings'
import { useOnboarding } from 'frappe-ui/frappe'
const editor = ref(null)
const instructorEditor = ref(null)
const user = inject('$user')
const openInstructorEditor = ref(false)
const settingsStore = useSettings()
const { updateOnboardingStep } = useOnboarding('learning')
let autoSaveInterval
let showSuccessMessage = false
@@ -395,10 +395,8 @@ const createNewLesson = () => {
{
onSuccess() {
capture('lesson_created')
updateOnboardingStep('create_first_lesson')
showToast('Success', 'Lesson created successfully', 'check')
/* if (!settingsStore.onboardingDetails.data?.is_onboarded) {
settingsStore.onboardingDetails.reload()
} */
lessonDetails.reload()
},
}

View File

@@ -211,6 +211,7 @@ import { Plus, Trash2 } from 'lucide-vue-next'
import Question from '@/components/Modals/Question.vue'
import { showToast, updateDocumentTitle } from '@/utils'
import { useRouter } from 'vue-router'
import { useOnboarding } from 'frappe-ui/frappe'
const showQuestionModal = ref(false)
const currentQuestion = reactive({
@@ -220,6 +221,7 @@ const currentQuestion = reactive({
})
const user = inject('$user')
const router = useRouter()
const { updateOnboardingStep } = useOnboarding('learning')
const props = defineProps({
quizID: {
@@ -337,6 +339,7 @@ const createQuiz = () => {
{
onSuccess(data) {
showToast(__('Success'), __('Quiz created successfully'), 'check')
updateOnboardingStep('create_first_quiz')
router.push({
name: 'QuizForm',
params: { quizID: data.name },