2026-01-15 12:12:05 +01:00

234 lines
5.5 KiB
TypeScript

import { useState, useEffect } from 'react'
import { Button, Modal } from './ui'
import { t } from '@/lib/i18n'
import { isOnboardingCompleted, markOnboardingCompleted } from '@/lib/onboardingPreferences'
interface OnboardingStep {
id: string
title: string
content: string
targetSelector?: string
}
const ONBOARDING_STEPS: OnboardingStep[] = [
{
id: 'welcome',
title: t('onboarding.steps.welcome.title'),
content: t('onboarding.steps.welcome.content'),
},
{
id: 'search',
title: t('onboarding.steps.search.title'),
content: t('onboarding.steps.search.content'),
targetSelector: '[role="search"]',
},
{
id: 'filters',
title: t('onboarding.steps.filters.title'),
content: t('onboarding.steps.filters.content'),
targetSelector: '#filters-section',
},
{
id: 'articles',
title: t('onboarding.steps.articles.title'),
content: t('onboarding.steps.articles.content'),
targetSelector: '#articles-section',
},
{
id: 'payment',
title: t('onboarding.steps.payment.title'),
content: t('onboarding.steps.payment.content'),
},
]
interface OnboardingTourProps {
onComplete?: () => void
}
interface OnboardingHandlersParams {
isLastStep: boolean
currentStep: number
setCurrentStep: (value: number) => void
setIsActive: (value: boolean) => void
onComplete?: () => void
}
function useOnboardingState(): {
isActive: boolean
setIsActive: (value: boolean) => void
currentStep: number
setCurrentStep: (value: number) => void
isLoading: boolean
} {
const [isActive, setIsActive] = useState(false)
const [currentStep, setCurrentStep] = useState(0)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const checkOnboarding = async (): Promise<void> => {
try {
const completed = await isOnboardingCompleted()
if (!completed) {
setIsActive(true)
}
} catch (error) {
console.error('Error checking onboarding status:', error)
} finally {
setIsLoading(false)
}
}
void checkOnboarding()
}, [])
return { isActive, setIsActive, currentStep, setCurrentStep, isLoading }
}
function useStepScrolling(isActive: boolean, currentStep: number): void {
useEffect(() => {
if (isActive && currentStep < ONBOARDING_STEPS.length) {
const step = ONBOARDING_STEPS[currentStep]
if (step?.targetSelector) {
const element = document.querySelector(step.targetSelector)
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}
}
}, [isActive, currentStep])
}
function OnboardingTourActions({
currentStep,
isLastStep,
onNext,
onSkip,
}: {
currentStep: number
isLastStep: boolean
onNext: () => void
onSkip: () => void
}): React.ReactElement {
const stepText = t('onboarding.step', { current: currentStep + 1, total: ONBOARDING_STEPS.length })
return (
<div className="flex justify-between items-center">
<div className="text-xs text-cyber-accent/70">
{stepText}
</div>
<div className="flex gap-2">
<Button
type="button"
variant="ghost"
size="small"
onClick={onSkip}
>
{t('onboarding.skip')}
</Button>
<Button
type="button"
variant="primary"
size="small"
onClick={onNext}
>
{isLastStep ? t('onboarding.finish') : t('onboarding.next')}
</Button>
</div>
</div>
)
}
function OnboardingTourContent({
step,
currentStep,
isLastStep,
onNext,
onSkip,
}: {
step: OnboardingStep
currentStep: number
isLastStep: boolean
onNext: () => void
onSkip: () => void
}): React.ReactElement {
return (
<div className="space-y-4">
<p className="text-cyber-accent">{step.content}</p>
<OnboardingTourActions
currentStep={currentStep}
isLastStep={isLastStep}
onNext={onNext}
onSkip={onSkip}
/>
</div>
)
}
function useOnboardingHandlers(params: OnboardingHandlersParams): {
handleNext: () => void
handleSkip: () => void
} {
const handleComplete = async (): Promise<void> => {
try {
await markOnboardingCompleted()
params.setIsActive(false)
params.onComplete?.()
} catch (error) {
console.error('Error marking onboarding as completed:', error)
}
}
const handleNext = (): void => {
if (params.isLastStep) {
void handleComplete()
} else {
params.setCurrentStep(params.currentStep + 1)
}
}
const handleSkip = (): void => {
void handleComplete()
}
return { handleNext, handleSkip }
}
export function OnboardingTour({ onComplete }: OnboardingTourProps): React.ReactElement | null {
const { isActive, setIsActive, currentStep, setCurrentStep, isLoading } = useOnboardingState()
useStepScrolling(isActive, currentStep)
if (isLoading || !isActive) {
return null
}
const step = ONBOARDING_STEPS[currentStep]
if (!step) {
return null
}
const isLastStep = currentStep === ONBOARDING_STEPS.length - 1
const { handleNext, handleSkip } = useOnboardingHandlers({
isLastStep,
currentStep,
setCurrentStep,
setIsActive,
onComplete,
})
return (
<Modal
isOpen={isActive}
onClose={handleSkip}
title={step.title}
size="medium"
>
<OnboardingTourContent
step={step}
currentStep={currentStep}
isLastStep={isLastStep}
onNext={handleNext}
onSkip={handleSkip}
/>
</Modal>
)
}