Files
grateful-journal/src/hooks/useOnboardingTour.ts
2026-04-08 11:01:53 +05:30

228 lines
6.1 KiB
TypeScript

import { useCallback, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { driver, type DriveStep } from 'driver.js'
import 'driver.js/dist/driver.css'
const TOUR_PENDING_KEY = 'gj-tour-pending-step'
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-edit-profile',
popover: {
title: 'Edit Your Profile',
description: 'Tap the pencil icon to change your display name or profile photo.',
side: isMobile ? 'bottom' : 'bottom',
align: 'center',
},
},
{
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 [isTourActive, setIsTourActive] = useState(false)
const startTour = useCallback(() => {
const isMobile = window.innerWidth < 860
setIsTourActive(true)
const driverObj = driver({
...driverDefaults(),
onDestroyStarted: () => {
clearPendingTourStep()
setIsTourActive(false)
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')
setIsTourActive(false)
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: () => {
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: () => {
clearPendingTourStep()
driverObj.destroy()
},
onNextClick: () => {
const activeIndex = driverObj.getActiveIndex()
const steps = driverObj.getConfig().steps || []
// Last settings step → navigate to /
if (activeIndex === steps.length - 1) {
clearPendingTourStep()
driverObj.destroy()
navigate('/write')
return
}
driverObj.moveNext()
},
steps: getSettingsSteps(isMobile),
})
driverRef.current = driverObj
setTimeout(() => driverObj.drive(), 300)
}, [navigate])
return { startTour, continueTourOnHistory, continueTourOnSettings, isTourActive }
}