added driverjs onboarding
This commit is contained in:
216
src/hooks/useOnboardingTour.ts
Normal file
216
src/hooks/useOnboardingTour.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { driver, type DriveStep } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
|
||||
const ONBOARDING_KEY = 'gj-onboarding-done'
|
||||
const TOUR_PENDING_KEY = 'gj-tour-pending-step'
|
||||
|
||||
export function hasSeenOnboarding(): boolean {
|
||||
return localStorage.getItem(ONBOARDING_KEY) === 'true'
|
||||
}
|
||||
|
||||
export function markOnboardingDone(): void {
|
||||
localStorage.setItem(ONBOARDING_KEY, 'true')
|
||||
}
|
||||
|
||||
export function hasPendingTourStep(): string | null {
|
||||
return localStorage.getItem(TOUR_PENDING_KEY)
|
||||
}
|
||||
|
||||
export function clearPendingTourStep(): void {
|
||||
localStorage.removeItem(TOUR_PENDING_KEY)
|
||||
}
|
||||
|
||||
function driverDefaults() {
|
||||
return {
|
||||
showProgress: true,
|
||||
animate: true,
|
||||
allowClose: true,
|
||||
overlayColor: 'rgba(0, 0, 0, 0.6)',
|
||||
stagePadding: 8,
|
||||
stageRadius: 12,
|
||||
popoverClass: 'gj-tour-popover',
|
||||
nextBtnText: 'Next',
|
||||
prevBtnText: 'Back',
|
||||
doneBtnText: 'Got it!',
|
||||
progressText: '{{current}} of {{total}}',
|
||||
} as const
|
||||
}
|
||||
|
||||
function getHomeSteps(isMobile: boolean): DriveStep[] {
|
||||
return [
|
||||
{
|
||||
element: '#tour-title-input',
|
||||
popover: {
|
||||
title: 'Give it a Title',
|
||||
description: 'Start by naming your gratitude entry. A short title helps you find it later.',
|
||||
side: 'bottom',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '#tour-content-textarea',
|
||||
popover: {
|
||||
title: 'Write Your Thoughts',
|
||||
description: 'Pour out what you\'re grateful for today. There\'s no right or wrong — just write from the heart.',
|
||||
side: isMobile ? 'top' : 'bottom',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '#tour-save-btn',
|
||||
popover: {
|
||||
title: 'Save Your Entry',
|
||||
description: 'Hit save and your entry is securely encrypted and stored. Only you can read it.',
|
||||
side: 'top',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '#tour-nav-history',
|
||||
popover: {
|
||||
title: 'View Your History',
|
||||
description: 'This takes you to the History page. Let\'s go there next!',
|
||||
side: isMobile ? 'top' : 'right',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function getHistorySteps(isMobile: boolean): DriveStep[] {
|
||||
return [
|
||||
{
|
||||
element: '#tour-calendar',
|
||||
popover: {
|
||||
title: 'Your Calendar',
|
||||
description: 'Green dots mark days you wrote entries. Navigate between months using the arrows.',
|
||||
side: isMobile ? 'bottom' : 'right',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '#tour-entries-list',
|
||||
popover: {
|
||||
title: 'Your Past Entries',
|
||||
description: 'Tap any date on the calendar to see entries from that day. Tap an entry card to read the full content.',
|
||||
side: isMobile ? 'top' : 'left',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '#tour-nav-settings',
|
||||
popover: {
|
||||
title: 'Your Settings',
|
||||
description: 'Let\'s check out your settings next!',
|
||||
side: isMobile ? 'top' : 'right',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function getSettingsSteps(isMobile: boolean): DriveStep[] {
|
||||
return [
|
||||
{
|
||||
element: '#tour-theme-switcher',
|
||||
popover: {
|
||||
title: 'Pick Your Theme',
|
||||
description: 'Switch between Light and Dark mode. Your choice is saved automatically.',
|
||||
side: isMobile ? 'top' : 'bottom',
|
||||
align: 'center',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function useOnboardingTour() {
|
||||
const navigate = useNavigate()
|
||||
const driverRef = useRef<ReturnType<typeof driver> | null>(null)
|
||||
|
||||
const startTour = useCallback(() => {
|
||||
const isMobile = window.innerWidth < 860
|
||||
|
||||
const driverObj = driver({
|
||||
...driverDefaults(),
|
||||
onDestroyStarted: () => {
|
||||
markOnboardingDone()
|
||||
clearPendingTourStep()
|
||||
driverObj.destroy()
|
||||
},
|
||||
onNextClick: () => {
|
||||
const activeIndex = driverObj.getActiveIndex()
|
||||
const steps = driverObj.getConfig().steps || []
|
||||
|
||||
// Last home step → navigate to /history
|
||||
if (activeIndex === steps.length - 1) {
|
||||
localStorage.setItem(TOUR_PENDING_KEY, 'history')
|
||||
driverObj.destroy()
|
||||
navigate('/history')
|
||||
return
|
||||
}
|
||||
|
||||
driverObj.moveNext()
|
||||
},
|
||||
steps: getHomeSteps(isMobile),
|
||||
})
|
||||
|
||||
driverRef.current = driverObj
|
||||
setTimeout(() => driverObj.drive(), 150)
|
||||
}, [navigate])
|
||||
|
||||
const continueTourOnHistory = useCallback(() => {
|
||||
const isMobile = window.innerWidth < 860
|
||||
|
||||
const driverObj = driver({
|
||||
...driverDefaults(),
|
||||
onDestroyStarted: () => {
|
||||
markOnboardingDone()
|
||||
clearPendingTourStep()
|
||||
driverObj.destroy()
|
||||
},
|
||||
onNextClick: () => {
|
||||
const activeIndex = driverObj.getActiveIndex()
|
||||
const steps = driverObj.getConfig().steps || []
|
||||
|
||||
// Last history step → navigate to /settings
|
||||
if (activeIndex === steps.length - 1) {
|
||||
localStorage.setItem(TOUR_PENDING_KEY, 'settings')
|
||||
driverObj.destroy()
|
||||
navigate('/settings')
|
||||
return
|
||||
}
|
||||
|
||||
driverObj.moveNext()
|
||||
},
|
||||
steps: getHistorySteps(isMobile),
|
||||
})
|
||||
|
||||
driverRef.current = driverObj
|
||||
setTimeout(() => driverObj.drive(), 300)
|
||||
}, [navigate])
|
||||
|
||||
const continueTourOnSettings = useCallback(() => {
|
||||
const isMobile = window.innerWidth < 860
|
||||
|
||||
const driverObj = driver({
|
||||
...driverDefaults(),
|
||||
onDestroyStarted: () => {
|
||||
markOnboardingDone()
|
||||
clearPendingTourStep()
|
||||
driverObj.destroy()
|
||||
},
|
||||
onDestroyed: () => {
|
||||
markOnboardingDone()
|
||||
clearPendingTourStep()
|
||||
},
|
||||
steps: getSettingsSteps(isMobile),
|
||||
})
|
||||
|
||||
driverRef.current = driverObj
|
||||
setTimeout(() => driverObj.drive(), 300)
|
||||
}, [])
|
||||
|
||||
return { startTour, continueTourOnHistory, continueTourOnSettings }
|
||||
}
|
||||
Reference in New Issue
Block a user