profit-planet-frontend/src/app/components/TutorialModal.tsx
2026-05-03 22:20:17 +02:00

340 lines
14 KiB
TypeScript

'use client'
import { useTranslation } from '../i18n/useTranslation';
import { Fragment } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import {
XMarkIcon,
EnvelopeIcon,
IdentificationIcon,
UserIcon,
DocumentTextIcon,
ClockIcon,
ArrowRightIcon,
CheckCircleIcon,
HandRaisedIcon,
HeartIcon,
CheckIcon
} from '@heroicons/react/24/outline'
interface TutorialStep {
id: number
title: string
description: string
details: string[]
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
buttonText: string
buttonAction: () => void
canProceed: boolean
}
interface TutorialModalProps {
isOpen: boolean
onClose: () => void
currentStep: number
steps: TutorialStep[]
onNext: () => void
onPrevious: () => void
}
export default function TutorialModal({
isOpen,
onClose,
currentStep,
steps,
onNext,
onPrevious
}: TutorialModalProps) {
const { t } = useTranslation();
const step = steps[currentStep - 1]
if (!step) return null
const isLastStep = currentStep === steps.length
const isFirstStep = currentStep === 1
// Helper function to check if step is completed
const isStepCompleted = (stepId: number) => {
if (stepId === 2) return step.buttonText.includes("✅") // Email verified
if (stepId === 3) return step.buttonText.includes("✅") // ID uploaded
if (stepId === 4) return step.buttonText.includes("✅") // Profile completed
if (stepId === 5) return step.buttonText.includes("✅") // Agreement signed
return false
}
// Get clean button text without emoji
const getCleanButtonText = (text: string) => {
return text.replace(/✅/g, '').trim().replace(/!$/, '')
}
const stepCompleted = isStepCompleted(step.id)
const buttonText = stepCompleted ? getCleanButtonText(step.buttonText) : step.buttonText
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-blue-900/60 backdrop-blur-sm transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10">
<div className="flex min-h-full items-center justify-center p-3 sm:p-4">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
{/* CHANGED: mobile uses max-height + scrolling */}
<Dialog.Panel className="relative w-full max-w-5xl max-h-[88dvh] sm:h-[60vh]">
<div className="relative isolate h-full overflow-hidden rounded-2xl bg-slate-50 after:pointer-events-none after:absolute after:inset-0 after:inset-ring after:inset-ring-gray-200/50 sm:rounded-3xl after:sm:rounded-3xl lg:flex lg:gap-x-12 lg:px-8 w-full">
{/* Background Gradient */}
<svg
viewBox="0 0 1024 1024"
aria-hidden="true"
className="absolute -top-48 -left-48 -z-10 size-96 mask-[radial-gradient(closest-side,white,transparent)]"
>
<circle r={512} cx={512} cy={512} fill="url(#tutorial-gradient)" fillOpacity="0.7" />
<defs>
<radialGradient id="tutorial-gradient">
<stop stopColor="#3B82F6" />
<stop offset={1} stopColor="#8B5CF6" />
</radialGradient>
</defs>
</svg>
{/* Close Button */}
<button
type="button"
className="absolute right-4 top-4 z-10 rounded-md bg-gray-200/70 p-2 text-gray-600 hover:text-gray-800 hover:bg-gray-300/70 focus:outline-none focus:ring-2 focus:ring-blue-500 backdrop-blur-sm"
onClick={onClose}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
{/* CHANGED: content scrolls on mobile */}
<div className="lg:flex-1 lg:max-w-md text-center lg:text-left px-5 py-6 sm:px-6 sm:py-8 flex flex-col justify-start overflow-y-auto">
{/* Icon */}
<div className="mx-auto lg:mx-0 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 mb-4 ring-2 ring-blue-200">
<step.icon className="h-6 w-6 text-blue-600" aria-hidden="true" />
</div>
{/* CHANGED: allow wrapping on mobile (remove nowrap) */}
<h2 className="text-xl font-semibold tracking-tight text-gray-800 sm:text-2xl">
{step.title}
</h2>
{/* CHANGED: no fixed height clipping on mobile */}
<p className="mt-3 text-sm text-gray-600 leading-relaxed sm:h-12 sm:overflow-hidden">
{step.description}
</p>
{/* Details */}
<ul className="mt-4 text-left text-gray-600 space-y-2">
{step.details.map((detail, index) => (
<li key={index} className="flex items-start gap-2">
<div className="h-1.5 w-1.5 rounded-full bg-blue-500 mt-1.5 flex-shrink-0" />
<span className="text-xs leading-5">{detail}</span>
</li>
))}
</ul>
{/* Progress indicator */}
<div className="mt-6">
<div className="flex items-center justify-between text-xs text-gray-500 mb-2">
<span>Step {currentStep} of {steps.length}</span>
<span>{Math.round((currentStep / steps.length) * 100)}% Complete</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div
className="bg-gradient-to-r from-blue-500 to-purple-500 h-1.5 rounded-full transition-all duration-500"
style={{ width: `${(currentStep / steps.length) * 100}%` }}
/>
</div>
</div>
{/* Action Buttons */}
<div className="mt-6 flex items-center justify-center gap-x-3 lg:justify-start">
<button
type="button"
onClick={step.buttonAction}
disabled={(!step.canProceed && currentStep !== steps.length) || buttonText.includes("Waiting for admin review") || stepCompleted}
className={`rounded-md px-3 py-2 text-sm font-semibold inset-ring inset-ring-gray-200/50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 transition-all flex items-center gap-2 ${
stepCompleted
? 'bg-green-600 text-white'
: (step.canProceed || currentStep === steps.length) && !buttonText.includes("Waiting for admin review")
? 'bg-blue-600 text-white hover:bg-blue-500 shadow-lg hover:shadow-xl'
: 'bg-gray-300 text-gray-500'
}`}
>
{stepCompleted && <CheckIcon className="h-4 w-4" />}
{buttonText}
</button>
</div>
{/* Navigation Buttons */}
{(!isFirstStep || !isLastStep) && (
<div className="mt-4 flex items-center justify-center gap-x-4 lg:justify-start">
<button
type="button"
onClick={onPrevious}
disabled={isFirstStep}
className={`text-xs font-semibold transition-colors ${
isFirstStep
? 'text-slate-50 cursor-default'
: 'text-gray-500 hover:text-gray-700'
}`}
>{t('autofix.kccc13f16')}</button>
{!isLastStep && (
<button
type="button"
onClick={onNext}
className="text-xs font-semibold text-blue-600 hover:text-blue-700 transition-colors"
>{t('autofix.ka3cbb536')}</button>
)}
</div>
)}
</div>
{/* CHANGED: hide unused visual section on mobile */}
<div className="relative hidden lg:flex lg:flex-1 mt-4 lg:mt-0 h-32 lg:h-full lg:min-h-[150px] items-end justify-end">
{/* <img
src="/images/misc/cow.png"
alt={t('autofix.kcc1c5596')}
className="max-h-full max-w-full object-contain opacity-90 pl-30"
/> */}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
)
}
// Tutorial step data
export const createTutorialSteps = (
emailVerified: boolean,
idUploaded: boolean,
additionalInfo: boolean,
contractSigned: boolean,
userType: string,
onVerifyEmail: () => void,
onUploadId: () => void,
onCompleteInfo: () => void,
onSignContract: () => void,
onCloseTutorial: () => void,
onNext: () => void
): TutorialStep[] => [
{
id: 1,
title: "Hello there! 👋 Welcome to Profit Planet",
description: "We're so happy you've decided to join us! This quick tutorial will guide you through setting up your account in just a few simple steps. Let's make this journey together - it'll only take a few minutes!",
details: [
"We'll walk you through each step personally",
"Everything is designed to be simple and clear",
"You can skip steps if you want to come back later",
"Our team is here to help if you need anything"
],
icon: HandRaisedIcon,
buttonText: "Let's get started! 🚀",
buttonAction: onNext,
canProceed: true
},
{
id: 2,
title: "Let's verify your email address 📧",
description: "First things first - we'd love to make sure we can reach you! Please check your email inbox for a friendly message from us and the verification code.",
details: [
"Check your email inbox for our welcome message",
"Copy & paste the verification code into the field",
"Don't see it? Check your spam folder - sometimes it hides there",
"Need a new email? Just click below and we'll send another"
],
icon: EnvelopeIcon,
buttonText: emailVerified ? "Email verified! ✅" : "Verify my email",
buttonAction: emailVerified ? onNext : onVerifyEmail,
canProceed: true
},
{
id: 3,
title: "Time to upload your ID 📋",
description: "Now we need to get to know you better! Please upload a clear photo of your official ID. Don't worry - this information is completely secure and helps us keep everyone safe.",
details: [
"Take a clear, well-lit photo of your ID document",
"Make sure all text is easily readable",
"Passport, driver's license, or national ID all work perfectly",
"We protect your privacy - this is just for verification"
],
icon: IdentificationIcon,
buttonText: idUploaded ? "ID uploaded! ✅" : "Upload my ID",
buttonAction: idUploaded ? onNext : onUploadId,
canProceed: true
},
{
id: 4,
title: "Complete your profile 👤",
description: `Almost there! Now let's fill out your ${userType === 'personal' ? 'personal' : 'company'} profile. This helps us customize your experience and ensure everything runs smoothly.`,
details: userType === 'personal' ? [
"Share your full name and date of birth with us",
"Add your current address (we keep this private)",
"Include a phone number so we can reach you if needed",
"All information is required for account security"
] : [
"Tell us about your company and business details",
"Add your business address and contact information",
"Upload any business documents we might need",
"Make sure everything matches your official records"
],
icon: UserIcon,
buttonText: additionalInfo ? "Profile completed! ✅" : "Complete my profile",
buttonAction: additionalInfo ? onNext : onCompleteInfo,
canProceed: true
},
{
id: 5,
title: "Ready to sign your contract! 📝",
description: "Perfect! You've completed all the preparation steps. Now it's time to review and sign your personalized contract to finalize your account setup.",
details: [
"Review the terms and conditions carefully",
"Your contract has been prepared based on your information",
"Digital signature makes the process quick and secure",
"This is the final step to activate your account"
],
icon: DocumentTextIcon,
buttonText: contractSigned ? "Contract signed! ✅" : "Review & sign contract",
buttonAction: contractSigned ? onNext : onSignContract,
canProceed: emailVerified && idUploaded && additionalInfo
},
{
id: 6,
title: "You're all set! 🎉 Welcome to the family",
description: "Congratulations! Our team will now review your information and have you approved very soon!",
details: [
"Our team will carefully review everything you've submitted",
"This usually takes just 1-2 business days",
"We'll send you a celebratory email once you're approved",
"In the meantime, feel free to explore your dashboard"
],
icon: HeartIcon,
buttonText: "Perfect! I understand 💫",
buttonAction: onCloseTutorial,
canProceed: true
}
]