199 lines
9.2 KiB
TypeScript
199 lines
9.2 KiB
TypeScript
import { useState } from 'react'
|
||
import vgn1 from '../assets/virtualguardednetwork1.png'
|
||
import vgn2 from '../assets/virtualguardednetwork2.png'
|
||
import './VirtualGuardedNetworkTour.css'
|
||
|
||
type TourStep = {
|
||
id: string
|
||
title: string
|
||
description: string
|
||
image: string
|
||
region: { leftPct: number; topPct: number; widthPct: number; heightPct: number }
|
||
popupPosition?: { leftPct?: number; topPct?: number; rightPct?: number; bottomPct?: number }
|
||
}
|
||
|
||
const vgnSteps: TourStep[] = [
|
||
{
|
||
id: 'overview',
|
||
title: 'Virtual Guarded Network (VGN) Overview',
|
||
description: 'VGN bölümüne hoş geldiniz! Zincirler, erişim, MFA politikaları ve logları buradan yönetebilirsiniz.',
|
||
image: vgn1,
|
||
region: { leftPct: 0, topPct: 0, widthPct: 100, heightPct: 100 },
|
||
popupPosition: { rightPct: 35, topPct: 60 },
|
||
},
|
||
{
|
||
id: 'chain-header',
|
||
title: 'Chain Header & Controls',
|
||
description: 'Zincirin adı, mod, MTU, DHCP ve routing gibi meta veriler bu başlıkta yer alır. Arama ve Add New ile yeni zincirler ekleyebilirsiniz.',
|
||
image: vgn1,
|
||
region: { leftPct: 15, topPct: 2, widthPct: 84, heightPct: 10 },
|
||
popupPosition: { rightPct: 2, topPct: 14 },
|
||
},
|
||
{
|
||
id: 'chain-flow',
|
||
title: 'Chain Flow',
|
||
description: 'Kullanıcı girişinden başlayıp, iç düğümler ve Internet Exit\'e giden görsel akış burada gösterilir.',
|
||
image: vgn1,
|
||
region: { leftPct: 15, topPct: 16, widthPct: 84, heightPct: 43 },
|
||
popupPosition: { leftPct: 15, bottomPct: 20 },
|
||
},
|
||
{
|
||
id: 'actions-and-policies',
|
||
title: 'Actions & Policies',
|
||
description: 'Sağ üstte yer alan VGN Chains, Access, MFA Policies ve Logs butonları ile politikaları ve günlükleri yönetebilirsiniz.',
|
||
image: vgn2,
|
||
region: { leftPct: 71, topPct: 7, widthPct: 24, heightPct: 7 },
|
||
popupPosition: { rightPct: 8, topPct: 16 },
|
||
},
|
||
]
|
||
|
||
type VirtualGuardedNetworkTourProps = {
|
||
autoStart?: boolean
|
||
onClose?: () => void
|
||
}
|
||
|
||
export default function VirtualGuardedNetworkTour({ autoStart = false, onClose }: VirtualGuardedNetworkTourProps) {
|
||
const [isModalOpen, setIsModalOpen] = useState(autoStart)
|
||
const [currentStep, setCurrentStep] = useState(0)
|
||
const [isTransitioning, setIsTransitioning] = useState(false)
|
||
const [isModalAnimating, setIsModalAnimating] = useState(false)
|
||
const [transitionDirection, setTransitionDirection] = useState<'next' | 'prev'>('next')
|
||
|
||
const handleStartTour = () => {
|
||
setIsModalOpen(true)
|
||
setCurrentStep(0)
|
||
}
|
||
|
||
const handleClose = () => {
|
||
setIsModalAnimating(true)
|
||
if (onClose) onClose()
|
||
setTimeout(() => {
|
||
setIsModalOpen(false)
|
||
setCurrentStep(0)
|
||
setIsTransitioning(false)
|
||
setTimeout(() => setIsModalAnimating(false), 50)
|
||
}, 250)
|
||
}
|
||
|
||
const handleNext = () => {
|
||
if (currentStep < vgnSteps.length - 1) {
|
||
setTransitionDirection('next')
|
||
setIsTransitioning(true)
|
||
setTimeout(() => {
|
||
setCurrentStep(currentStep + 1)
|
||
setTimeout(() => setIsTransitioning(false), 50)
|
||
}, 300)
|
||
}
|
||
}
|
||
|
||
const handlePrevious = () => {
|
||
if (currentStep > 0) {
|
||
setTransitionDirection('prev')
|
||
setIsTransitioning(true)
|
||
setTimeout(() => {
|
||
setCurrentStep(currentStep - 1)
|
||
setTimeout(() => setIsTransitioning(false), 50)
|
||
}, 300)
|
||
}
|
||
}
|
||
|
||
const handleStepClick = (stepIndex: number) => {
|
||
if (stepIndex !== currentStep) {
|
||
setTransitionDirection(stepIndex > currentStep ? 'next' : 'prev')
|
||
setIsTransitioning(true)
|
||
setTimeout(() => {
|
||
setCurrentStep(stepIndex)
|
||
setTimeout(() => setIsTransitioning(false), 50)
|
||
}, 300)
|
||
}
|
||
}
|
||
|
||
const activeStep = vgnSteps[currentStep]
|
||
|
||
if (!autoStart && !isModalOpen) {
|
||
return (
|
||
<div className="vgn-tour-container">
|
||
<div className="vgn-preview-section">
|
||
<div className="vgn-preview-image-wrapper">
|
||
<img src={vgn1} alt="VGN Preview" className="vgn-preview-image" />
|
||
<div className="vgn-preview-overlay">
|
||
<button className="start-tour-button" onClick={handleStartTour}>
|
||
<span className="button-icon">▶</span>
|
||
Start VGN Tour
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<div className={`vgn-modal-backdrop ${isModalAnimating ? 'closing' : ''}`} onClick={handleClose}>
|
||
<div className={`vgn-modal-content ${isModalAnimating ? 'closing' : ''}`} onClick={(e) => e.stopPropagation()}>
|
||
<div className="vgn-modal-image-wrapper">
|
||
<img className="vgn-modal-image" src={activeStep.image} alt="VGN" />
|
||
|
||
{currentStep > 0 && (
|
||
<>
|
||
{activeStep.region.topPct > 0 && (
|
||
<div className={`vgn-overlay-top ${isTransitioning ? 'vgn-overlay-transitioning' : ''}`} style={{ height: `${activeStep.region.topPct}%` }} />
|
||
)}
|
||
{activeStep.region.leftPct > 0 && (
|
||
<div className={`vgn-overlay-left ${isTransitioning ? 'vgn-overlay-transitioning' : ''}`} style={{ top: `${activeStep.region.topPct}%`, width: `${activeStep.region.leftPct}%`, height: `${activeStep.region.heightPct}%` }} />
|
||
)}
|
||
{activeStep.region.leftPct + activeStep.region.widthPct < 100 && (
|
||
<div className={`vgn-overlay-right ${isTransitioning ? 'vgn-overlay-transitioning' : ''}`} style={{ top: `${activeStep.region.topPct}%`, left: `${activeStep.region.leftPct + activeStep.region.widthPct}%`, height: `${activeStep.region.heightPct}%` }} />
|
||
)}
|
||
{activeStep.region.topPct + activeStep.region.heightPct < 100 && (
|
||
<div className={`vgn-overlay-bottom ${isTransitioning ? 'vgn-overlay-transitioning' : ''}`} style={{ top: `${activeStep.region.topPct + activeStep.region.heightPct}%` }} />
|
||
)}
|
||
<div className={`vgn-highlight-border ${isTransitioning ? 'transitioning' : ''}`} style={{ left: `${activeStep.region.leftPct}%`, top: `${activeStep.region.topPct}%`, width: `${activeStep.region.widthPct}%`, height: `${activeStep.region.heightPct}%` }} />
|
||
</>
|
||
)}
|
||
|
||
<div
|
||
className={`vgn-step-popup ${isTransitioning ? `transitioning transition-${transitionDirection}` : ''} ${isModalAnimating ? 'closing' : ''}`}
|
||
style={{
|
||
...(activeStep.popupPosition?.leftPct && { left: `${activeStep.popupPosition.leftPct}%` }),
|
||
...(activeStep.popupPosition?.topPct && { top: `${activeStep.popupPosition.topPct}%` }),
|
||
...(activeStep.popupPosition?.rightPct && { right: `${activeStep.popupPosition.rightPct}%` }),
|
||
...(activeStep.popupPosition?.bottomPct && { bottom: `${activeStep.popupPosition.bottomPct}%` }),
|
||
}}
|
||
>
|
||
<div className="vgn-popup-animated-border" />
|
||
<button className="vgn-quit-button" onClick={handleClose}>×</button>
|
||
|
||
<div className="vgn-popup-content">
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '12px' }}>
|
||
<div style={{ background: '#b02f34', color: 'white', fontSize: '12px', padding: '4px 8px', borderRadius: '999px', fontWeight: 600 }}>
|
||
{currentStep + 1}/{vgnSteps.length}
|
||
</div>
|
||
<h3 style={{ margin: 0, fontSize: '18px', fontWeight: 600, color: '#f1f2f5' }}>{activeStep.title}</h3>
|
||
</div>
|
||
<p style={{ margin: 0, fontSize: '14px', lineHeight: 1.6, color: '#b9bfca' }}>{activeStep.description}</p>
|
||
<div style={{ display: 'flex', gap: '10px', marginTop: '20px', justifyContent: 'space-between', alignItems: 'center' }}>
|
||
<button onClick={handlePrevious} disabled={currentStep === 0} style={{ background: currentStep === 0 ? 'rgba(0,0,0,0.3)' : '#1a1d26', border: '1px solid #262a33', color: currentStep === 0 ? '#555' : '#b9bfca', padding: '8px 16px', borderRadius: '6px', cursor: currentStep === 0 ? 'not-allowed' : 'pointer', fontSize: '14px', fontWeight: 500, transition: 'all 200ms ease' }}>← Previous</button>
|
||
<button onClick={currentStep === vgnSteps.length - 1 ? handleClose : handleNext} style={{ background: 'linear-gradient(135deg, #b02f34 0%, #8a252a 100%)', border: '1px solid #b02f34', color: 'white', padding: '8px 20px', borderRadius: '6px', cursor: 'pointer', fontSize: '14px', fontWeight: 600, transition: 'all 200ms ease' }}>{currentStep === vgnSteps.length - 1 ? 'Finish' : 'Next →'}</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className={`vgn-tour-progress-bar ${isModalAnimating ? 'closing' : ''}`}>
|
||
{vgnSteps.map((step, index) => (
|
||
<div key={step.id} className={`vgn-progress-segment ${index < currentStep ? 'completed' : ''} ${index === currentStep ? 'active' : ''}`} onClick={() => handleStepClick(index)} title={step.title}>
|
||
<div className="vgn-progress-segment-fill" />
|
||
<div className={`vgn-progress-step-number ${index < currentStep ? 'completed' : ''} ${index === currentStep ? 'active' : ''}`}>{index + 1}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
|