Files
Dramlog-Prod/src/components/OnboardingTutorial.tsx
robin 73a057b1e3 feat: Hide tutorial on login, add 'Angemeldet bleiben' checkbox
- OnboardingTutorial: Skip on /login, /auth, /register paths
- AuthForm: Added remember-me checkbox (default: checked)
- Session persistence based on checkbox selection
2025-12-26 22:46:22 +01:00

167 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { motion, AnimatePresence } from 'framer-motion';
import { Scan, GlassWater, Users, Settings, ArrowRight, X, Sparkles } from 'lucide-react';
const ONBOARDING_KEY = 'dramlog_onboarding_complete';
interface OnboardingStep {
id: string;
icon: React.ReactNode;
title: string;
description: string;
}
const STEPS: OnboardingStep[] = [
{
id: 'welcome',
icon: <Sparkles size={32} className="text-orange-500" />,
title: 'Willkommen bei DramLog!',
description: 'Dein persönliches Whisky-Tagebuch. Scanne, bewerte und entdecke neue Drams.',
},
{
id: 'scan',
icon: <Scan size={32} className="text-orange-500" />,
title: 'Scanne deine Flaschen',
description: 'Fotografiere das Etikett einer Flasche die KI erkennt automatisch alle Details.',
},
{
id: 'taste',
icon: <GlassWater size={32} className="text-orange-500" />,
title: 'Bewerte deine Drams',
description: 'Füge Tasting-Notizen hinzu und behalte den Überblick über deine Lieblings-Whiskys.',
},
{
id: 'session',
icon: <Users size={32} className="text-orange-500" />,
title: 'Tasting-Sessions',
description: 'Organisiere Verkostungen mit Freunden und vergleicht eure Bewertungen.',
},
{
id: 'ready',
icon: <Settings size={32} className="text-orange-500" />,
title: 'Bereit zum Start!',
description: 'Scanne jetzt deine erste Flasche mit dem orangefarbenen Button unten.',
},
];
export default function OnboardingTutorial() {
const [isOpen, setIsOpen] = useState(false);
const [currentStep, setCurrentStep] = useState(0);
const pathname = usePathname();
useEffect(() => {
// Don't show on login/auth pages
if (pathname === '/login' || pathname === '/auth' || pathname === '/register') {
return;
}
// Check if onboarding was completed
const completed = localStorage.getItem(ONBOARDING_KEY);
if (!completed) {
// Small delay to not overwhelm on first load
const timer = setTimeout(() => setIsOpen(true), 1000);
return () => clearTimeout(timer);
}
}, [pathname]);
const handleNext = () => {
if (currentStep < STEPS.length - 1) {
setCurrentStep(prev => prev + 1);
} else {
handleComplete();
}
};
const handleComplete = () => {
localStorage.setItem(ONBOARDING_KEY, 'true');
setIsOpen(false);
};
const handleSkip = () => {
localStorage.setItem(ONBOARDING_KEY, 'true');
setIsOpen(false);
};
if (!isOpen) return null;
const step = STEPS[currentStep];
const isLastStep = currentStep === STEPS.length - 1;
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[200] bg-black/90 backdrop-blur-sm flex items-center justify-center p-6"
>
{/* Close button */}
<button
onClick={handleSkip}
className="absolute top-6 right-6 p-2 text-zinc-500 hover:text-white transition-colors"
>
<X size={24} />
</button>
<motion.div
key={step.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="max-w-sm w-full"
>
{/* Icon */}
<div className="w-20 h-20 mx-auto rounded-3xl bg-zinc-900 border border-zinc-800 flex items-center justify-center mb-8">
{step.icon}
</div>
{/* Content */}
<h2 className="text-2xl font-bold text-white text-center mb-3">
{step.title}
</h2>
<p className="text-zinc-400 text-center text-sm leading-relaxed mb-8">
{step.description}
</p>
{/* Progress dots */}
<div className="flex justify-center gap-2 mb-8">
{STEPS.map((_, index) => (
<div
key={index}
className={`w-2 h-2 rounded-full transition-all ${index === currentStep
? 'w-6 bg-orange-500'
: index < currentStep
? 'bg-orange-500/50'
: 'bg-zinc-700'
}`}
/>
))}
</div>
{/* Buttons */}
<div className="flex gap-3">
{!isLastStep && (
<button
onClick={handleSkip}
className="flex-1 py-3 px-4 text-sm font-bold text-zinc-500 hover:text-white transition-colors"
>
Überspringen
</button>
)}
<button
onClick={handleNext}
className="flex-1 py-3 px-4 bg-orange-600 hover:bg-orange-500 text-white font-bold text-sm rounded-xl flex items-center justify-center gap-2 transition-colors"
>
{isLastStep ? 'Los geht\'s!' : 'Weiter'}
<ArrowRight size={16} />
</button>
</div>
</motion.div>
</motion.div>
</AnimatePresence>
);
}