340 lines
14 KiB
TypeScript
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
|
|
}
|
|
] |