Merge pull request #691 from shariquerik/onboarding-fix

fix: Onboarding fixes
This commit is contained in:
Shariq Ansari 2025-03-24 14:16:21 +05:30 committed by GitHub
commit 48845491d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 92 additions and 86 deletions

Binary file not shown.

Binary file not shown.

@ -1 +1 @@
Subproject commit b6efd25b2122c2c1f3beeea1702788c5e6e5555f Subproject commit 3423aa5b5c38d3a1b143ae8ab08cbde7360f9a7c

View File

@ -192,7 +192,6 @@ declare module 'vue' {
SidePanelLayout: typeof import('./src/components/SidePanelLayout.vue')['default'] SidePanelLayout: typeof import('./src/components/SidePanelLayout.vue')['default']
SidePanelLayoutEditor: typeof import('./src/components/SidePanelLayoutEditor.vue')['default'] SidePanelLayoutEditor: typeof import('./src/components/SidePanelLayoutEditor.vue')['default']
SidePanelModal: typeof import('./src/components/Modals/SidePanelModal.vue')['default'] SidePanelModal: typeof import('./src/components/Modals/SidePanelModal.vue')['default']
SignupBanner: typeof import('./src/components/SignupBanner.vue')['default']
SLASection: typeof import('./src/components/SLASection.vue')['default'] SLASection: typeof import('./src/components/SLASection.vue')['default']
SmileIcon: typeof import('./src/components/Icons/SmileIcon.vue')['default'] SmileIcon: typeof import('./src/components/Icons/SmileIcon.vue')['default']
SortBy: typeof import('./src/components/SortBy.vue')['default'] SortBy: typeof import('./src/components/SortBy.vue')['default']

View File

@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@twilio/voice-sdk": "^2.10.2", "@twilio/voice-sdk": "^2.10.2",
"@vueuse/integrations": "^10.3.0", "@vueuse/integrations": "^10.3.0",
"frappe-ui": "^0.1.119", "frappe-ui": "^0.1.120",
"gemoji": "^8.1.0", "gemoji": "^8.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mime": "^4.0.1", "mime": "^4.0.1",

View File

@ -72,14 +72,24 @@
</div> </div>
</div> </div>
<div class="m-2 flex flex-col gap-1"> <div class="m-2 flex flex-col gap-1">
<SignupBanner :isSidebarCollapsed="isSidebarCollapsed" /> <div class="flex flex-col gap-2 mb-1">
<TrialBanner v-if="isFCSite" :isSidebarCollapsed="isSidebarCollapsed" /> <SignupBanner
<GettingStartedBanner v-if="isDemoSite"
v-if="!isOnboardingStepsCompleted" :isSidebarCollapsed="isSidebarCollapsed"
:isSidebarCollapsed="isSidebarCollapsed" :afterSignup="() => capture('signup_from_demo_site')"
/> />
<TrialBanner
v-if="isFCSite"
:isSidebarCollapsed="isSidebarCollapsed"
:afterUpgrade="() => capture('upgrade_plan_from_trial_banner')"
/>
<GettingStartedBanner
v-if="!isOnboardingStepsCompleted"
:isSidebarCollapsed="isSidebarCollapsed"
/>
</div>
<SidebarLink <SidebarLink
v-else v-if="isOnboardingStepsCompleted"
:label="__('Help')" :label="__('Help')"
:isCollapsed="isSidebarCollapsed" :isCollapsed="isSidebarCollapsed"
@click=" @click="
@ -118,9 +128,14 @@
:logo="CRMLogo" :logo="CRMLogo"
:afterSkip="(step) => capture('onboarding_step_skipped_' + step)" :afterSkip="(step) => capture('onboarding_step_skipped_' + step)"
:afterSkipAll="() => capture('onboarding_steps_skipped')" :afterSkipAll="() => capture('onboarding_steps_skipped')"
:afterReset="() => capture('onboarding_steps_reset')" :afterReset="(step) => capture('onboarding_step_reset_' + step)"
:afterResetAll="() => capture('onboarding_steps_reset')"
docsLink="https://docs.frappe.io/crm" docsLink="https://docs.frappe.io/crm"
/> />
<IntermediateStepModal
v-model="showIntermediateModal"
:currentStep="currentStep"
/>
</div> </div>
</template> </template>
@ -148,7 +163,6 @@ import HelpIcon from '@/components/Icons/HelpIcon.vue'
import SidebarLink from '@/components/SidebarLink.vue' 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 { viewsStore } from '@/stores/views' import { viewsStore } from '@/stores/views'
import { import {
unreadNotificationsCount, unreadNotificationsCount,
@ -157,12 +171,14 @@ import {
import { showSettings, activeSettingsPage } from '@/composables/settings' import { showSettings, activeSettingsPage } from '@/composables/settings'
import { FeatherIcon, call } from 'frappe-ui' import { FeatherIcon, call } from 'frappe-ui'
import { import {
SignupBanner,
TrialBanner, TrialBanner,
HelpModal, HelpModal,
GettingStartedBanner, GettingStartedBanner,
useOnboarding, useOnboarding,
showHelpModal, showHelpModal,
minimize, minimize,
IntermediateStepModal,
} from 'frappe-ui/frappe' } from 'frappe-ui/frappe'
import { capture } from '@/telemetry' import { capture } from '@/telemetry'
import router from '@/router' import router from '@/router'
@ -175,6 +191,7 @@ const { toggle: toggleNotificationPanel } = notificationsStore()
const isSidebarCollapsed = useStorage('isSidebarCollapsed', false) const isSidebarCollapsed = useStorage('isSidebarCollapsed', false)
const isFCSite = ref(window.is_fc_site) const isFCSite = ref(window.is_fc_site)
const isDemoSite = ref(window.is_demo_site)
const links = [ const links = [
{ {
@ -284,19 +301,21 @@ function getIcon(routeName, icon) {
// onboarding // onboarding
const { isOnboardingStepsCompleted, setUp } = useOnboarding('frappecrm') const { isOnboardingStepsCompleted, setUp } = useOnboarding('frappecrm')
const firstLead = ref('')
const firstDeal = ref('')
async function getFirstLead() { async function getFirstLead() {
if (firstLead.value) return firstLead.value let firstLead = localStorage.getItem('firstLead')
if (firstLead) return firstLead
return await call('crm.api.onboarding.get_first_lead') return await call('crm.api.onboarding.get_first_lead')
} }
async function getFirstDeal() { async function getFirstDeal() {
if (firstDeal.value) return firstDeal.value let firstDeal = localStorage.getItem('firstDeal')
if (firstDeal) return firstDeal
return await call('crm.api.onboarding.get_first_deal') return await call('crm.api.onboarding.get_first_deal')
} }
const showIntermediateModal = ref(false)
const currentStep = ref({})
const steps = reactive([ const steps = reactive([
{ {
name: 'create_first_lead', name: 'create_first_lead',
@ -327,13 +346,23 @@ const steps = reactive([
onClick: async () => { onClick: async () => {
minimize.value = true minimize.value = true
let lead = await getFirstLead() currentStep.value = {
title: __('Convert lead to deal'),
buttonLabel: __('Convert'),
videoURL: '/assets/crm/videos/convertToDeal.mov',
onClick: async () => {
showIntermediateModal.value = false
currentStep.value = {}
if (lead) { let lead = await getFirstLead()
router.push({ name: 'Lead', params: { leadId: lead } }) if (lead) {
} else { router.push({ name: 'Lead', params: { leadId: lead } })
router.push({ name: 'Leads' }) } else {
router.push({ name: 'Leads' })
}
},
} }
showIntermediateModal.value = true
}, },
}, },
{ {
@ -423,17 +452,28 @@ const steps = reactive([
completed: false, completed: false,
onClick: async () => { onClick: async () => {
minimize.value = true minimize.value = true
let deal = await getFirstDeal()
if (deal) { currentStep.value = {
router.push({ title: __('Change deal status'),
name: 'Deal', buttonLabel: __('Change'),
params: { dealId: deal }, videoURL: '/assets/crm/videos/changeDealStatus.mov',
hash: '#activity', onClick: async () => {
}) showIntermediateModal.value = false
} else { currentStep.value = {}
router.push({ name: 'Leads' })
let deal = await getFirstDeal()
if (deal) {
router.push({
name: 'Deal',
params: { dealId: deal },
hash: '#activity',
})
} else {
router.push({ name: 'Leads' })
}
},
} }
showIntermediateModal.value = true
}, },
}, },
]) ])

View File

@ -168,7 +168,9 @@ function createNewLead() {
isLeadCreating.value = false isLeadCreating.value = false
show.value = false show.value = false
router.push({ name: 'Lead', params: { leadId: data.name } }) router.push({ name: 'Lead', params: { leadId: data.name } })
updateOnboardingStep('create_first_lead') updateOnboardingStep('create_first_lead', true, false, () => {
localStorage.setItem('firstLead', data.name)
})
}, },
onError(err) { onError(err) {
isLeadCreating.value = false isLeadCreating.value = false

View File

@ -1,41 +0,0 @@
<template>
<div
v-if="!isSidebarCollapsed && showBanner"
class="m-2 flex flex-col gap-3 shadow-sm rounded-lg py-2.5 px-3 bg-surface-white text-base"
>
<div class="flex flex-col gap-1">
<div class="inline-flex gap-2 items-center font-medium">
<FeatherIcon class="h-4" name="info" />
{{ __('Loved the demo?') }}
</div>
<div class="text-ink-gray-7 text-p-sm">
{{ __('Try Frappe CRM for free with a 14-day trial.') }}
</div>
</div>
<Button :label="__('Sign up now')" theme="blue" @click="signupNow">
<template #prefix>
<LightningIcon class="size-4" />
</template>
</Button>
</div>
</template>
<script setup>
import LightningIcon from '@/components/Icons/LightningIcon.vue'
import { capture } from '@/telemetry'
import { createResource } from 'frappe-ui'
import { ref } from 'vue'
const props = defineProps({
isSidebarCollapsed: {
type: Boolean,
default: false,
},
})
const showBanner = ref(window.is_demo_site)
function signupNow() {
capture('signup_from_demo_site')
window.open('https://frappecloud.com/crm/signup', '_blank')
}
</script>

View File

@ -657,7 +657,9 @@ async function convertToDeal() {
existingOrganizationChecked.value = false existingOrganizationChecked.value = false
existingContact.value = '' existingContact.value = ''
existingOrganization.value = '' existingOrganization.value = ''
updateOnboardingStep('convert_lead_to_deal') updateOnboardingStep('convert_lead_to_deal', true, false, () => {
localStorage.setItem('firstDeal', _deal)
})
capture('convert_lead_to_deal') capture('convert_lead_to_deal')
router.push({ name: 'Deal', params: { dealId: _deal } }) router.push({ name: 'Deal', params: { dealId: _deal } })
} }

View File

@ -1,9 +1,13 @@
{ {
"private": true, "private": true,
"workspaces": ["frontend", "frappe-ui"], "workspaces": ["frontend", "frappe-ui"],
"scripts": { "scripts": {
"postinstall": "cd frontend && yarn install", "postinstall": "cd frontend && yarn install",
"dev": "cd frontend && yarn dev", "dev": "cd frontend && yarn dev",
"build": "cd frontend && yarn build" "build": "cd frontend && yarn build",
} "disable-workspaces": "sed -i '' 's/\"workspaces\"/\"aworkspaces\"/g' package.json",
} "enable-workspaces": "sed -i '' 's/\"aworkspaces\"/\"workspaces\"/g' package.json && rm -rf node_modules ./frontend/node_modules/ frappe-ui/node_modules/ && yarn install",
"upgrade-frappeui": "cd frontend && yarn add frappe-ui@latest && cd ..",
"disable-workspaces-and-upgrade-frappeui": "yarn disable-workspaces && yarn upgrade-frappeui"
}
}

View File

@ -2542,10 +2542,10 @@ fraction.js@^4.3.7:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
frappe-ui@^0.1.119: frappe-ui@^0.1.120:
version "0.1.119" version "0.1.120"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.119.tgz#e41ff3a7ea3ff21008587564c3cefcea86e8d244" resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.120.tgz#0ccbc626bc44ef62b0b253f2b759bb9deca1c6fd"
integrity sha512-qgJE3I+N+AxwTsJuTU8O1zDfSePxI8oKm+Cn6RmHTOp6SwdiRvgEu/btKXityAY6nZf6A7OAgqUI4cVHXsr7mQ== integrity sha512-vwXo5Jgu5HInvA0hXpP44ItIYqK4USx0flNeT80Ffygg3uBPE7ZV9B3cWVd3RRjxWtrZvSDG0XTmXq7uuE4e9g==
dependencies: dependencies:
"@headlessui/vue" "^1.7.14" "@headlessui/vue" "^1.7.14"
"@popperjs/core" "^2.11.2" "@popperjs/core" "^2.11.2"