feat: onboarding steps
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
<AppSidebar />
|
||||
</div>
|
||||
<div class="w-full overflow-auto" id="scrollContainer">
|
||||
<OnboardingBanner />
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,4 +17,5 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import AppSidebar from './AppSidebar.vue'
|
||||
import OnboardingBanner from '@/components/OnboardingBanner.vue'
|
||||
</script>
|
||||
|
||||
@@ -81,9 +81,11 @@ import { defineModel, reactive, watch } from 'vue'
|
||||
import { showToast, getFileSize } from '@/utils/'
|
||||
import { capture } from '@/telemetry'
|
||||
import { FileText, X } from 'lucide-vue-next'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
|
||||
const show = defineModel()
|
||||
const outline = defineModel('outline')
|
||||
const settingsStore = useSettings()
|
||||
|
||||
const props = defineProps({
|
||||
course: {
|
||||
@@ -143,6 +145,9 @@ const addChapter = async (close) => {
|
||||
{
|
||||
onSuccess(data) {
|
||||
cleanChapter()
|
||||
if (!settingsStore.onboardingDetails.data?.is_onboarded) {
|
||||
settingsStore.onboardingDetails.reload()
|
||||
}
|
||||
outline.value.reload()
|
||||
showToast(
|
||||
__('Success'),
|
||||
|
||||
151
frontend/src/components/OnboardingBanner.vue
Normal file
151
frontend/src/components/OnboardingBanner.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div v-if="showOnboardingBanner && onboardingDetails.data">
|
||||
<Tooltip :text="__('Skip Onboarding')" placement="left">
|
||||
<X
|
||||
class="w-4 h-4 stroke-1 absolute top-2 right-2 cursor-pointer mr-1"
|
||||
@click="skipOnboarding.reload()"
|
||||
/>
|
||||
</Tooltip>
|
||||
<div class="flex items-center justify-evenly bg-gray-100 p-10">
|
||||
<div
|
||||
@click="redirectToCourseForm()"
|
||||
class="flex items-center space-x-2"
|
||||
:class="{
|
||||
'cursor-pointer': !onboardingDetails.data.course_created.length,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="onboardingDetails.data.course_created.length"
|
||||
class="py-1 px-1 bg-white rounded-full"
|
||||
>
|
||||
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
||||
</span>
|
||||
<span v-else class="font-semibold bg-white px-2 py-1 rounded-full">
|
||||
1
|
||||
</span>
|
||||
<span class="text-lg font-semibold">
|
||||
{{ __('Create a course') }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@click="redirectToChapterForm()"
|
||||
class="flex items-center space-x-2"
|
||||
:class="{
|
||||
'cursor-pointer':
|
||||
onboardingDetails.data.course_created.length &&
|
||||
!onboardingDetails.data.chapter_created.length,
|
||||
'text-gray-400': !onboardingDetails.data.course_created.length,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="onboardingDetails.data.chapter_created.length"
|
||||
class="py-1 px-1 bg-white rounded-full"
|
||||
>
|
||||
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
||||
</span>
|
||||
<span v-else class="font-semibold bg-white px-2 py-1 rounded-full">
|
||||
2
|
||||
</span>
|
||||
<span class="text-lg font-semibold">
|
||||
{{ __('Add a chapter') }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@click="redirectToLessonForm()"
|
||||
class="flex items-center space-x-2"
|
||||
:class="{
|
||||
'cursor-pointer':
|
||||
onboardingDetails.data.course_created.length &&
|
||||
onboardingDetails.data.chapter_created.length,
|
||||
'text-gray-400':
|
||||
!onboardingDetails.data.course_created.length ||
|
||||
!onboardingDetails.data.chapter_created.length,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="onboardingDetails.data.lesson_created.length"
|
||||
class="py-1 px-1 bg-white rounded-full"
|
||||
>
|
||||
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
||||
</span>
|
||||
<span class="font-semibold bg-white px-2 py-1 rounded-full"> 3 </span>
|
||||
<span class="text-lg font-semibold">
|
||||
{{ __('Add a lesson') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { Check, X } from 'lucide-vue-next'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useSettings } from '@/stores/settings'
|
||||
import { createResource, Tooltip } from 'frappe-ui'
|
||||
|
||||
const showOnboardingBanner = ref(false)
|
||||
const settings = useSettings()
|
||||
const onboardingDetails = settings.onboardingDetails
|
||||
const router = useRouter()
|
||||
|
||||
watch(onboardingDetails, () => {
|
||||
if (!onboardingDetails.data?.is_onboarded) {
|
||||
showOnboardingBanner.value = true
|
||||
} else {
|
||||
showOnboardingBanner.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const redirectToCourseForm = () => {
|
||||
if (onboardingDetails.data?.course_created.length) {
|
||||
return
|
||||
} else {
|
||||
router.push({ name: 'CourseForm', params: { courseName: 'new' } })
|
||||
}
|
||||
}
|
||||
|
||||
const redirectToChapterForm = () => {
|
||||
if (!onboardingDetails.data?.course_created.length) {
|
||||
return
|
||||
} else {
|
||||
router.push({
|
||||
name: 'CourseForm',
|
||||
params: {
|
||||
courseName: onboardingDetails.data?.first_course,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const redirectToLessonForm = () => {
|
||||
if (!onboardingDetails.data?.course_created.length) {
|
||||
return
|
||||
} else if (!onboardingDetails.data?.chapter_created.length) {
|
||||
return
|
||||
} else {
|
||||
router.push({
|
||||
name: 'LessonForm',
|
||||
params: {
|
||||
courseName: onboardingDetails.data?.first_course,
|
||||
chapterNumber: 1,
|
||||
lessonNumber: 1,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const skipOnboarding = createResource({
|
||||
url: 'frappe.client.set_value',
|
||||
makeParams() {
|
||||
return {
|
||||
doctype: 'LMS Settings',
|
||||
name: 'LMS Settings',
|
||||
fieldname: 'is_onboarding_complete',
|
||||
value: 1,
|
||||
}
|
||||
},
|
||||
onSuccess(data) {
|
||||
onboardingDetails.reload()
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -434,6 +434,9 @@ const submitCourse = () => {
|
||||
onSuccess(data) {
|
||||
capture('course_created')
|
||||
showToast('Success', 'Course created successfully', 'check')
|
||||
if (!settingsStore.onboardingDetails.data?.is_onboarded) {
|
||||
settingsStore.onboardingDetails.reload()
|
||||
}
|
||||
router.push({
|
||||
name: 'CourseForm',
|
||||
params: { courseName: data.name },
|
||||
|
||||
@@ -92,11 +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'
|
||||
|
||||
const editor = ref(null)
|
||||
const instructorEditor = ref(null)
|
||||
const user = inject('$user')
|
||||
const openInstructorEditor = ref(false)
|
||||
const settingsStore = useSettings()
|
||||
let autoSaveInterval
|
||||
let showSuccessMessage = false
|
||||
|
||||
@@ -393,6 +395,9 @@ const createNewLesson = () => {
|
||||
onSuccess() {
|
||||
capture('lesson_created')
|
||||
showToast('Success', 'Lesson created successfully', 'check')
|
||||
if (!settingsStore.onboardingDetails.data?.is_onboarded) {
|
||||
settingsStore.onboardingDetails.reload()
|
||||
}
|
||||
lessonDetails.reload()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -17,9 +17,16 @@ export const useSettings = defineStore('settings', () => {
|
||||
cache: ['learningPaths'],
|
||||
})
|
||||
|
||||
const onboardingDetails = createResource({
|
||||
url: 'lms.lms.utils.is_onboarding_complete',
|
||||
auto: true,
|
||||
cache: ['onboardingDetails'],
|
||||
})
|
||||
|
||||
return {
|
||||
isSettingsOpen,
|
||||
activeTab,
|
||||
learningPaths,
|
||||
onboardingDetails,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user