fix: use onboarding/help components & composable from frappe ui
This commit is contained in:
parent
6ebcd0e887
commit
4326582c5b
@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
v-if="!isSidebarCollapsed"
|
|
||||||
class="m-2 flex flex-col gap-3 shadow-sm rounded-lg py-2.5 px-3 bg-surface-white text-base"
|
|
||||||
>
|
|
||||||
<div v-if="stepsCompleted != totalSteps" class="inline-flex gap-2">
|
|
||||||
<StepsIcon class="h-4 my-0.5 shrink-0" />
|
|
||||||
<div class="flex flex-col text-p-sm gap-0.5">
|
|
||||||
<div class="text-ink-gray-9 font-medium">
|
|
||||||
{{ __('Gettings started') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-ink-gray-7">
|
|
||||||
{{ __('{0}/{1} steps', [stepsCompleted, totalSteps]) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex flex-col gap-1">
|
|
||||||
<div class="flex items-center justify-between gap-1">
|
|
||||||
<div class="flex items-center gap-2 shrink-0">
|
|
||||||
<StepsIcon class="h-4 my-0.5" />
|
|
||||||
<div class="text-ink-gray-9 font-medium">
|
|
||||||
{{ __('You are all set!') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FeatherIcon
|
|
||||||
name="x"
|
|
||||||
class="h-4 cursor-pointer"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
emit('showHelpCenter')
|
|
||||||
isOnboardingStepsCompleted = true
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="text-p-sm text-ink-gray-7">
|
|
||||||
{{ __('All steps are completed successfully!') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
v-if="stepsCompleted != totalSteps"
|
|
||||||
:label="__('Complete now')"
|
|
||||||
theme="blue"
|
|
||||||
@click="emit('completeNow')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import StepsIcon from '@/components/Icons/StepsIcon.vue'
|
|
||||||
import {
|
|
||||||
isOnboardingStepsCompleted,
|
|
||||||
useOnboarding,
|
|
||||||
} from '@/composables/onboarding'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
isSidebarCollapsed: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['completeNow', 'showHelpCenter'])
|
|
||||||
|
|
||||||
const { stepsCompleted, totalSteps } = useOnboarding()
|
|
||||||
</script>
|
|
||||||
@ -77,13 +77,6 @@
|
|||||||
<GettingStartedBanner
|
<GettingStartedBanner
|
||||||
v-if="!isOnboardingStepsCompleted"
|
v-if="!isOnboardingStepsCompleted"
|
||||||
:isSidebarCollapsed="isSidebarCollapsed"
|
:isSidebarCollapsed="isSidebarCollapsed"
|
||||||
@completeNow="
|
|
||||||
() => {
|
|
||||||
minimize = false
|
|
||||||
showHelpModal = true
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@showHelpCenter="showHelpCenter = true"
|
|
||||||
/>
|
/>
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
v-else
|
v-else
|
||||||
@ -113,15 +106,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<Settings />
|
<Settings />
|
||||||
<HelpModal
|
<HelpModal v-if="showHelpModal" v-model="showHelpModal" :logo="CRMLogo" />
|
||||||
v-if="showHelpModal"
|
|
||||||
v-model="showHelpModal"
|
|
||||||
v-model:showHelpCenter="showHelpCenter"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import CRMLogo from '@/components/Icons/CRMLogo.vue'
|
||||||
|
import InviteIcon from '@/components/Icons/InviteIcon.vue'
|
||||||
|
import ConvertIcon from '@/components/Icons/ConvertIcon.vue'
|
||||||
|
import CommentIcon from '@/components/Icons/CommentIcon.vue'
|
||||||
|
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
||||||
|
import StepsIcon from '@/components/Icons/StepsIcon.vue'
|
||||||
import Section from '@/components/Section.vue'
|
import Section from '@/components/Section.vue'
|
||||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||||
import PinIcon from '@/components/Icons/PinIcon.vue'
|
import PinIcon from '@/components/Icons/PinIcon.vue'
|
||||||
@ -140,17 +135,24 @@ import SidebarLink from '@/components/SidebarLink.vue'
|
|||||||
import Notifications from '@/components/Notifications.vue'
|
import Notifications from '@/components/Notifications.vue'
|
||||||
import Settings from '@/components/Settings/Settings.vue'
|
import Settings from '@/components/Settings/Settings.vue'
|
||||||
import SignupBanner from '@/components/SignupBanner.vue'
|
import SignupBanner from '@/components/SignupBanner.vue'
|
||||||
import GettingStartedBanner from '@/components/GettingStartedBanner.vue'
|
|
||||||
import HelpModal from '@/components/Modals/HelpModal.vue'
|
|
||||||
import { viewsStore } from '@/stores/views'
|
import { viewsStore } from '@/stores/views'
|
||||||
import {
|
import {
|
||||||
unreadNotificationsCount,
|
unreadNotificationsCount,
|
||||||
notificationsStore,
|
notificationsStore,
|
||||||
} from '@/stores/notifications'
|
} from '@/stores/notifications'
|
||||||
import { isOnboardingStepsCompleted, minimize } from '@/composables/onboarding'
|
import { showSettings, activeSettingsPage } from '@/composables/settings'
|
||||||
import { FeatherIcon, TrialBanner } from 'frappe-ui'
|
import {
|
||||||
|
FeatherIcon,
|
||||||
|
TrialBanner,
|
||||||
|
HelpModal,
|
||||||
|
GettingStartedBanner,
|
||||||
|
useOnboarding,
|
||||||
|
showHelpModal,
|
||||||
|
minimize,
|
||||||
|
} from 'frappe-ui'
|
||||||
|
import router from '@/router'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { ref, computed, h } from 'vue'
|
import { ref, reactive, computed, h, markRaw, onMounted } from 'vue'
|
||||||
|
|
||||||
const { getPinnedViews, getPublicViews } = viewsStore()
|
const { getPinnedViews, getPublicViews } = viewsStore()
|
||||||
const { toggle: toggleNotificationPanel } = notificationsStore()
|
const { toggle: toggleNotificationPanel } = notificationsStore()
|
||||||
@ -264,6 +266,162 @@ function getIcon(routeName, icon) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const showHelpModal = ref(false)
|
// onboarding
|
||||||
const showHelpCenter = ref(false)
|
const { isOnboardingStepsCompleted, setUp } = useOnboarding('frappecrm')
|
||||||
|
|
||||||
|
const firstLead = ref('')
|
||||||
|
const firstDeal = ref('')
|
||||||
|
|
||||||
|
async function getFirstLead() {
|
||||||
|
if (firstLead.value) return firstLead.value
|
||||||
|
return await call('crm.api.onboarding.get_first_lead')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFirstDeal() {
|
||||||
|
if (firstDeal.value) return firstDeal.value
|
||||||
|
return await call('crm.api.onboarding.get_first_deal')
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps = reactive([
|
||||||
|
{
|
||||||
|
name: 'create_first_lead',
|
||||||
|
title: 'Create your first lead',
|
||||||
|
icon: markRaw(LeadsIcon),
|
||||||
|
completed: true,
|
||||||
|
onClick: () => {
|
||||||
|
minimize.value = true
|
||||||
|
router.push({ name: 'Leads' })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invite_your_team',
|
||||||
|
title: 'Invite your team',
|
||||||
|
icon: markRaw(InviteIcon),
|
||||||
|
completed: false,
|
||||||
|
onClick: () => {
|
||||||
|
minimize.value = true
|
||||||
|
showSettings.value = true
|
||||||
|
activeSettingsPage.value = 'Invite Members'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'convert_lead_to_deal',
|
||||||
|
title: 'Convert lead to deal',
|
||||||
|
icon: markRaw(ConvertIcon),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
|
||||||
|
let lead = await getFirstLead()
|
||||||
|
|
||||||
|
if (lead) {
|
||||||
|
router.push({ name: 'Lead', params: { leadId: lead } })
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'Leads' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_first_task',
|
||||||
|
title: 'Create your first task',
|
||||||
|
icon: markRaw(TaskIcon),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let deal = await getFirstDeal()
|
||||||
|
|
||||||
|
if (deal) {
|
||||||
|
router.push({
|
||||||
|
name: 'Deal',
|
||||||
|
params: { dealId: deal },
|
||||||
|
hash: '#tasks',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'Tasks' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_first_note',
|
||||||
|
title: 'Create your first note',
|
||||||
|
icon: markRaw(NoteIcon),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let deal = await getFirstDeal()
|
||||||
|
|
||||||
|
if (deal) {
|
||||||
|
router.push({
|
||||||
|
name: 'Deal',
|
||||||
|
params: { dealId: deal },
|
||||||
|
hash: '#notes',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'Notes' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'add_first_comment',
|
||||||
|
title: 'Add your first comment',
|
||||||
|
icon: markRaw(CommentIcon),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let deal = await getFirstDeal()
|
||||||
|
|
||||||
|
if (deal) {
|
||||||
|
router.push({
|
||||||
|
name: 'Deal',
|
||||||
|
params: { dealId: deal },
|
||||||
|
hash: '#comments',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'Leads' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'send_first_email',
|
||||||
|
title: 'Send email',
|
||||||
|
icon: markRaw(EmailIcon),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let deal = await getFirstDeal()
|
||||||
|
|
||||||
|
if (deal) {
|
||||||
|
router.push({
|
||||||
|
name: 'Deal',
|
||||||
|
params: { dealId: deal },
|
||||||
|
hash: '#emails',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'Leads' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'change_deal_status',
|
||||||
|
title: 'Change deal status',
|
||||||
|
icon: markRaw(StepsIcon),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let deal = await getFirstDeal()
|
||||||
|
|
||||||
|
if (deal) {
|
||||||
|
router.push({
|
||||||
|
name: 'Deal',
|
||||||
|
params: { dealId: deal },
|
||||||
|
hash: '#activity',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'Leads' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
onMounted(() => setUp(steps))
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,101 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
v-show="show"
|
|
||||||
class="fixed z-20 w-80 h-[calc(100%_-_80px)] text-ink-gray-9 m-5 mt-[62px] p-3 flex gap-2 flex-col justify-between rounded-lg bg-surface-modal shadow-2xl"
|
|
||||||
:class="[minimize ? 'right-0 top-[calc(100%_-_110px)]' : 'right-0']"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="text-base font-medium ml-1">
|
|
||||||
{{ __(title) }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button @click="minimize = !minimize" variant="ghost">
|
|
||||||
<component
|
|
||||||
:is="minimize ? MaximizeIcon : MinimizeIcon"
|
|
||||||
class="h-3.5"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" @click="show = false">
|
|
||||||
<FeatherIcon name="x" class="h-3.5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="h-full overflow-hidden flex flex-col">
|
|
||||||
<OnboardingSteps v-if="!isOnboardingStepsCompleted && !showHelpCenter" />
|
|
||||||
</div>
|
|
||||||
<div v-for="item in footerItems" class="flex flex-col gap-1.5">
|
|
||||||
<div
|
|
||||||
class="w-full flex gap-2 items-center hover:bg-surface-gray-1 text-ink-gray-8 rounded px-2 py-1.5 cursor-pointer"
|
|
||||||
@click="item.onClick"
|
|
||||||
>
|
|
||||||
<component :is="item.icon" class="h-4" />
|
|
||||||
<div class="text-base">{{ __(item.label) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import StepsIcon from '@/components/Icons/StepsIcon.vue'
|
|
||||||
import MinimizeIcon from '@/components/Icons/MinimizeIcon.vue'
|
|
||||||
import MaximizeIcon from '@/components/Icons/MaximizeIcon.vue'
|
|
||||||
import HelpIcon from '@/components/Icons/HelpIcon.vue'
|
|
||||||
import OnboardingSteps from '@/components/OnboardingSteps.vue'
|
|
||||||
import {
|
|
||||||
isOnboardingStepsCompleted,
|
|
||||||
minimize,
|
|
||||||
useOnboarding,
|
|
||||||
} from '@/composables/onboarding'
|
|
||||||
import { onMounted, computed } from 'vue'
|
|
||||||
|
|
||||||
const show = defineModel()
|
|
||||||
const showHelpCenter = defineModel('showHelpCenter')
|
|
||||||
|
|
||||||
const title = computed(() => {
|
|
||||||
if (!isOnboardingStepsCompleted.value && !showHelpCenter.value) {
|
|
||||||
return __('Getting started')
|
|
||||||
} else if (showHelpCenter.value) {
|
|
||||||
return __('Help center')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const footerItems = computed(() => {
|
|
||||||
let items = [
|
|
||||||
{
|
|
||||||
icon: HelpIcon,
|
|
||||||
label: __('Help centre'),
|
|
||||||
onClick: () => {
|
|
||||||
useOnboarding().syncStatus()
|
|
||||||
showHelpCenter.value = true
|
|
||||||
},
|
|
||||||
condition: !isOnboardingStepsCompleted.value && !showHelpCenter.value,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: StepsIcon,
|
|
||||||
label: __('Getting started'),
|
|
||||||
onClick: () => (showHelpCenter.value = false),
|
|
||||||
condition: showHelpCenter.value && !isOnboardingStepsCompleted.value,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: StepsIcon,
|
|
||||||
label: __('Reset onboarding steps'),
|
|
||||||
onClick: resetOnboardingSteps,
|
|
||||||
condition: showHelpCenter.value && isOnboardingStepsCompleted.value,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return items.filter((item) => item.condition)
|
|
||||||
})
|
|
||||||
|
|
||||||
function resetOnboardingSteps() {
|
|
||||||
useOnboarding().reset()
|
|
||||||
isOnboardingStepsCompleted.value = false
|
|
||||||
showHelpCenter.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (isOnboardingStepsCompleted.value) {
|
|
||||||
showHelpCenter.value = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col justify-center items-center gap-1 mt-4 mb-7">
|
|
||||||
<CRMLogo class="size-10 shrink-0 rounded mb-4" />
|
|
||||||
<div class="text-base font-medium">
|
|
||||||
{{ __('Welcome to Frappe CRM') }}
|
|
||||||
</div>
|
|
||||||
<div class="text-p-base font-normal">
|
|
||||||
{{ __('{0}/{1} steps completed', [stepsCompleted, totalSteps]) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2.5 overflow-hidden">
|
|
||||||
<div class="flex justify-between items-center py-0.5">
|
|
||||||
<Badge
|
|
||||||
:label="__('{0}% completed', [completedPercentage])"
|
|
||||||
theme="orange"
|
|
||||||
size="lg"
|
|
||||||
/>
|
|
||||||
<div class="flex">
|
|
||||||
<Button
|
|
||||||
v-if="completedPercentage != 0"
|
|
||||||
variant="ghost"
|
|
||||||
:label="__('Reset')"
|
|
||||||
@click="reset"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
v-if="completedPercentage != 100"
|
|
||||||
variant="ghost"
|
|
||||||
:label="__('Skip all')"
|
|
||||||
@click="skipAll"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1.5 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
v-for="step in steps"
|
|
||||||
:key="step.title"
|
|
||||||
class="group w-full flex gap-2 justify-between items-center hover:bg-surface-gray-1 rounded px-2 py-1.5 cursor-pointer"
|
|
||||||
@click.stop="() => !step.completed && step.onClick()"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex gap-2 items-center"
|
|
||||||
:class="[step.completed ? 'text-ink-gray-5' : 'text-ink-gray-8']"
|
|
||||||
>
|
|
||||||
<component :is="step.icon" class="h-4" />
|
|
||||||
<div class="text-base" :class="{ 'line-through': step.completed }">
|
|
||||||
{{ step.title }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
v-if="!step.completed"
|
|
||||||
:label="__('Skip')"
|
|
||||||
class="!h-4 text-xs !text-ink-gray-6 hidden group-hover:flex"
|
|
||||||
@click="() => skip(step.name)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import CRMLogo from '@/components/Icons/CRMLogo.vue'
|
|
||||||
import { useOnboarding } from '@/composables/onboarding'
|
|
||||||
|
|
||||||
const emit = defineEmits(['close'])
|
|
||||||
|
|
||||||
const {
|
|
||||||
steps,
|
|
||||||
stepsCompleted,
|
|
||||||
totalSteps,
|
|
||||||
completedPercentage,
|
|
||||||
skip,
|
|
||||||
skipAll,
|
|
||||||
reset,
|
|
||||||
} = useOnboarding()
|
|
||||||
</script>
|
|
||||||
@ -1,294 +0,0 @@
|
|||||||
import LeadsIcon from '@/components/Icons/LeadsIcon.vue'
|
|
||||||
import InviteIcon from '@/components/Icons/InviteIcon.vue'
|
|
||||||
import ConvertIcon from '@/components/Icons/ConvertIcon.vue'
|
|
||||||
import NoteIcon from '@/components/Icons/NoteIcon.vue'
|
|
||||||
import CommentIcon from '@/components/Icons/CommentIcon.vue'
|
|
||||||
import EmailIcon from '@/components/Icons/EmailIcon.vue'
|
|
||||||
import TaskIcon from '@/components/Icons/TaskIcon.vue'
|
|
||||||
import StepsIcon from '@/components/Icons/StepsIcon.vue'
|
|
||||||
import { capture } from '@/telemetry'
|
|
||||||
import { showSettings, activeSettingsPage } from '@/composables/settings'
|
|
||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
import router from '@/router'
|
|
||||||
import { call } from 'frappe-ui'
|
|
||||||
import { ref, reactive, computed, markRaw } from 'vue'
|
|
||||||
|
|
||||||
export const minimize = ref(false)
|
|
||||||
|
|
||||||
export const isOnboardingStepsCompleted = useStorage(
|
|
||||||
'isOnboardingStepsCompleted',
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
|
|
||||||
const firstLead = ref('')
|
|
||||||
const firstDeal = ref('')
|
|
||||||
|
|
||||||
const steps = reactive([
|
|
||||||
{
|
|
||||||
name: 'create_first_lead',
|
|
||||||
title: 'Create your first lead',
|
|
||||||
icon: markRaw(LeadsIcon),
|
|
||||||
completed: false,
|
|
||||||
onClick: () => {
|
|
||||||
minimize.value = true
|
|
||||||
router.push({ name: 'Leads' })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'invite_your_team',
|
|
||||||
title: 'Invite your team',
|
|
||||||
icon: markRaw(InviteIcon),
|
|
||||||
completed: false,
|
|
||||||
onClick: () => {
|
|
||||||
minimize.value = true
|
|
||||||
showSettings.value = true
|
|
||||||
activeSettingsPage.value = 'Invite Members'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'convert_lead_to_deal',
|
|
||||||
title: 'Convert lead to deal',
|
|
||||||
icon: markRaw(ConvertIcon),
|
|
||||||
completed: false,
|
|
||||||
onClick: async () => {
|
|
||||||
minimize.value = true
|
|
||||||
|
|
||||||
let lead = await getFirstLead()
|
|
||||||
|
|
||||||
if (lead) {
|
|
||||||
router.push({ name: 'Lead', params: { leadId: lead } })
|
|
||||||
} else {
|
|
||||||
router.push({ name: 'Leads' })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'create_first_task',
|
|
||||||
title: 'Create your first task',
|
|
||||||
icon: markRaw(TaskIcon),
|
|
||||||
completed: false,
|
|
||||||
onClick: async () => {
|
|
||||||
minimize.value = true
|
|
||||||
let deal = await getFirstDeal()
|
|
||||||
|
|
||||||
if (deal) {
|
|
||||||
router.push({
|
|
||||||
name: 'Deal',
|
|
||||||
params: { dealId: deal },
|
|
||||||
hash: '#tasks',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
router.push({ name: 'Tasks' })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'create_first_note',
|
|
||||||
title: 'Create your first note',
|
|
||||||
icon: markRaw(NoteIcon),
|
|
||||||
completed: false,
|
|
||||||
onClick: async () => {
|
|
||||||
minimize.value = true
|
|
||||||
let deal = await getFirstDeal()
|
|
||||||
|
|
||||||
if (deal) {
|
|
||||||
router.push({
|
|
||||||
name: 'Deal',
|
|
||||||
params: { dealId: deal },
|
|
||||||
hash: '#notes',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
router.push({ name: 'Notes' })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'add_first_comment',
|
|
||||||
title: 'Add your first comment',
|
|
||||||
icon: markRaw(CommentIcon),
|
|
||||||
completed: false,
|
|
||||||
onClick: async () => {
|
|
||||||
minimize.value = true
|
|
||||||
let deal = await getFirstDeal()
|
|
||||||
|
|
||||||
if (deal) {
|
|
||||||
router.push({
|
|
||||||
name: 'Deal',
|
|
||||||
params: { dealId: deal },
|
|
||||||
hash: '#comments',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
router.push({ name: 'Leads' })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'send_first_email',
|
|
||||||
title: 'Send email',
|
|
||||||
icon: markRaw(EmailIcon),
|
|
||||||
completed: false,
|
|
||||||
onClick: async () => {
|
|
||||||
minimize.value = true
|
|
||||||
let deal = await getFirstDeal()
|
|
||||||
|
|
||||||
if (deal) {
|
|
||||||
router.push({
|
|
||||||
name: 'Deal',
|
|
||||||
params: { dealId: deal },
|
|
||||||
hash: '#emails',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
router.push({ name: 'Leads' })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'change_deal_status',
|
|
||||||
title: 'Change deal status',
|
|
||||||
icon: markRaw(StepsIcon),
|
|
||||||
completed: false,
|
|
||||||
onClick: async () => {
|
|
||||||
minimize.value = true
|
|
||||||
let deal = await getFirstDeal()
|
|
||||||
|
|
||||||
if (deal) {
|
|
||||||
router.push({
|
|
||||||
name: 'Deal',
|
|
||||||
params: { dealId: deal },
|
|
||||||
hash: '#activity',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
router.push({ name: 'Leads' })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
async function getFirstLead() {
|
|
||||||
if (firstLead.value) return firstLead.value
|
|
||||||
return await call('crm.api.onboarding.get_first_lead')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFirstDeal() {
|
|
||||||
if (firstDeal.value) return firstDeal.value
|
|
||||||
return await call('crm.api.onboarding.get_first_deal')
|
|
||||||
}
|
|
||||||
|
|
||||||
const stepsCompleted = computed(
|
|
||||||
() => steps.filter((step) => step.completed).length,
|
|
||||||
)
|
|
||||||
const totalSteps = ref(steps.length)
|
|
||||||
|
|
||||||
const completedPercentage = computed(() =>
|
|
||||||
Math.floor((stepsCompleted.value / totalSteps.value) * 100),
|
|
||||||
)
|
|
||||||
|
|
||||||
export function useOnboarding() {
|
|
||||||
syncStatus()
|
|
||||||
|
|
||||||
function skip(step) {
|
|
||||||
updateOnboardingStep(step, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function skipAll() {
|
|
||||||
updateAll(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
updateAll(false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateOnboardingStep(step, skipped = false) {
|
|
||||||
if (isOnboardingStepsCompleted.value) return
|
|
||||||
let user = window.user
|
|
||||||
if (!user) return false
|
|
||||||
|
|
||||||
if (!user.onboarding_status['frappe_crm_onboarding_status']) {
|
|
||||||
user.onboarding_status['frappe_crm_onboarding_status'] = steps.map(
|
|
||||||
(s) => {
|
|
||||||
return { name: s.name, completed: false }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let _steps = user.onboarding_status['frappe_crm_onboarding_status']
|
|
||||||
let index = _steps.findIndex((s) => s.name === step)
|
|
||||||
if (index !== -1) {
|
|
||||||
_steps[index].completed = true
|
|
||||||
steps[index].completed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
window.user = user
|
|
||||||
|
|
||||||
if (skipped) {
|
|
||||||
capture('onboarding_skipped_' + step)
|
|
||||||
} else {
|
|
||||||
capture('onboarding_' + step)
|
|
||||||
}
|
|
||||||
|
|
||||||
call('crm.api.onboarding.update_user_onboarding_status', {
|
|
||||||
steps: JSON.stringify(_steps),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateAll(value, reset = false) {
|
|
||||||
if (isOnboardingStepsCompleted.value && !reset) return
|
|
||||||
let user = window.user
|
|
||||||
if (!user) return false
|
|
||||||
|
|
||||||
let _steps
|
|
||||||
|
|
||||||
if (!user.onboarding_status['frappe_crm_onboarding_status']) {
|
|
||||||
user.onboarding_status['frappe_crm_onboarding_status'] = steps.map(
|
|
||||||
(s) => {
|
|
||||||
return { name: s.name, completed: value }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
_steps = user.onboarding_status['frappe_crm_onboarding_status']
|
|
||||||
_steps.forEach((step) => {
|
|
||||||
step.completed = value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
steps.forEach((step) => {
|
|
||||||
step.completed = value
|
|
||||||
})
|
|
||||||
|
|
||||||
window.user = user
|
|
||||||
|
|
||||||
capture(value ? 'onboarding_skipped_all' : 'onboarding_reset_steps')
|
|
||||||
|
|
||||||
call('crm.api.onboarding.update_user_onboarding_status', {
|
|
||||||
steps: JSON.stringify(_steps),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncStatus() {
|
|
||||||
if (isOnboardingStepsCompleted.value) return
|
|
||||||
let user = window.user
|
|
||||||
if (!user) return false
|
|
||||||
|
|
||||||
if (user.onboarding_status['frappe_crm_onboarding_status']) {
|
|
||||||
let _steps = user.onboarding_status['frappe_crm_onboarding_status']
|
|
||||||
_steps.forEach((step, index) => {
|
|
||||||
steps[index].completed = step.completed
|
|
||||||
})
|
|
||||||
isOnboardingStepsCompleted.value = _steps.every((step) => step.completed)
|
|
||||||
} else {
|
|
||||||
isOnboardingStepsCompleted.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
steps,
|
|
||||||
stepsCompleted,
|
|
||||||
totalSteps,
|
|
||||||
completedPercentage,
|
|
||||||
updateOnboardingStep,
|
|
||||||
skip,
|
|
||||||
skipAll,
|
|
||||||
reset,
|
|
||||||
syncStatus,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user