228 lines
6.1 KiB
TypeScript
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 }
|
|
}
|