Complete Sprint 3 Landing Page development with final 20%:
Footer Component:
- 4-column layout: Brand, Useful Links, Legal, Newsletter
- SVG social icons (GitHub, Twitter, LinkedIn, YouTube)
- Working newsletter form with success feedback
- Dark theme design (slate-900) with proper contrast
- Copyright and attribution footer
OnboardingWizard Component:
- 3-step wizard with progress indicator
- Step 1: Welcome with service explanation
- Step 2: Webhook generation (crypto.randomUUID)
- Step 3: Setup instructions with curl command
- UUID generation and clipboard copy functionality
- Mock implementation (no backend API calls)
Accessibility Features:
- aria-live=polite on wizard container
- aria-current=step for progress indication
- Focus management with useRef/useEffect
- Keyboard navigation support
- Proper ARIA labels on interactive elements
Integration:
- Added to components/index.ts exports
- Integrated in App.tsx after InteractiveDemo
- CTAs scroll to #onboarding section
- Footer placed at page bottom
Build Verification:
- TypeScript compilation: ✓ 0 errors
- CSS bundle: 34KB (gzipped 7KB)
- JS bundle: 238KB (gzipped 72KB)
Sprint 3 Status: 100% Complete ✅
Landing Page now includes:
- Hero, Problem/Solution, HowItWorks, Demo, Onboarding, Footer
Refs: docs/frontend_landing_plan.md
354 lines
14 KiB
TypeScript
354 lines
14 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
||
import {
|
||
ChevronRight,
|
||
ChevronLeft,
|
||
Check,
|
||
Copy,
|
||
Terminal,
|
||
Rocket,
|
||
Settings,
|
||
AlertCircle,
|
||
RefreshCw
|
||
} from 'lucide-react';
|
||
|
||
interface OnboardingWizardProps {
|
||
onComplete?: () => void;
|
||
}
|
||
|
||
export const OnboardingWizard: React.FC<OnboardingWizardProps> = ({ onComplete }) => {
|
||
const [currentStep, setCurrentStep] = useState(1);
|
||
const [webhookUrl, setWebhookUrl] = useState('');
|
||
const [copied, setCopied] = useState(false);
|
||
const [isGenerating, setIsGenerating] = useState(false);
|
||
const stepRef = useRef<HTMLDivElement>(null);
|
||
|
||
// Focus management for accessibility
|
||
useEffect(() => {
|
||
if (stepRef.current) {
|
||
stepRef.current.focus();
|
||
}
|
||
}, [currentStep]);
|
||
|
||
const generateWebhook = () => {
|
||
setIsGenerating(true);
|
||
// Simulate generation delay
|
||
setTimeout(() => {
|
||
const uuid = crypto.randomUUID();
|
||
setWebhookUrl(`https://logwhisperer.ai/webhook/${uuid}`);
|
||
setIsGenerating(false);
|
||
}, 1000);
|
||
};
|
||
|
||
const copyWebhook = () => {
|
||
navigator.clipboard.writeText(webhookUrl);
|
||
setCopied(true);
|
||
setTimeout(() => setCopied(false), 2000);
|
||
};
|
||
|
||
const copyCurlCommand = () => {
|
||
const command = `curl -fsSL https://logwhisperer.ai/install.sh | bash -s -- --webhook ${webhookUrl}`;
|
||
navigator.clipboard.writeText(command);
|
||
setCopied(true);
|
||
setTimeout(() => setCopied(false), 2000);
|
||
};
|
||
|
||
const nextStep = () => {
|
||
if (currentStep < 3) {
|
||
setCurrentStep(currentStep + 1);
|
||
} else {
|
||
onComplete?.();
|
||
}
|
||
};
|
||
|
||
const prevStep = () => {
|
||
if (currentStep > 1) {
|
||
setCurrentStep(currentStep - 1);
|
||
}
|
||
};
|
||
|
||
const getInstallCommand = () => {
|
||
return webhookUrl
|
||
? `curl -fsSL https://logwhisperer.ai/install.sh | bash -s -- --webhook ${webhookUrl}`
|
||
: 'curl -fsSL https://logwhisperer.ai/install.sh | bash';
|
||
};
|
||
|
||
const steps = [
|
||
{ number: 1, title: 'Benvenuto', icon: <Rocket className="w-5 h-5" /> },
|
||
{ number: 2, title: 'Webhook', icon: <Settings className="w-5 h-5" /> },
|
||
{ number: 3, title: 'Setup', icon: <Terminal className="w-5 h-5" /> },
|
||
];
|
||
|
||
return (
|
||
<section
|
||
id="onboarding"
|
||
className="w-full py-24 lg:py-32 bg-gradient-to-b from-indigo-50 to-white"
|
||
aria-labelledby="onboarding-heading"
|
||
>
|
||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
{/* Section Header */}
|
||
<div className="text-center mb-12">
|
||
<h2
|
||
id="onboarding-heading"
|
||
className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4"
|
||
>
|
||
Inizia in 3 semplici passi
|
||
</h2>
|
||
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
|
||
Configura LogWhisperer AI sul tuo server in meno di 5 minuti.
|
||
</p>
|
||
</div>
|
||
|
||
{/* Step Indicator */}
|
||
<nav aria-label="Progresso onboarding" className="mb-12">
|
||
<ol className="flex items-center justify-center gap-4">
|
||
{steps.map((step, index) => (
|
||
<li key={step.number} className="flex items-center">
|
||
<div
|
||
className={`flex items-center gap-2 px-4 py-2 rounded-full font-medium text-sm transition-colors ${
|
||
currentStep === step.number
|
||
? 'bg-indigo-600 text-white'
|
||
: currentStep > step.number
|
||
? 'bg-green-100 text-green-700'
|
||
: 'bg-slate-200 text-slate-600'
|
||
}`}
|
||
aria-current={currentStep === step.number ? 'step' : undefined}
|
||
>
|
||
<span className="flex items-center justify-center w-6 h-6 rounded-full bg-white/20">
|
||
{currentStep > step.number ? (
|
||
<Check className="w-4 h-4" />
|
||
) : (
|
||
step.number
|
||
)}
|
||
</span>
|
||
<span className="hidden sm:inline">{step.title}</span>
|
||
</div>
|
||
{index < steps.length - 1 && (
|
||
<ChevronRight className="w-5 h-5 text-slate-300 mx-2" aria-hidden="true" />
|
||
)}
|
||
</li>
|
||
))}
|
||
</ol>
|
||
</nav>
|
||
|
||
{/* Wizard Content */}
|
||
<div
|
||
ref={stepRef}
|
||
tabIndex={-1}
|
||
className="bg-white rounded-2xl shadow-xl border border-slate-200 p-8 outline-none"
|
||
aria-live="polite"
|
||
>
|
||
{/* Step 1: Welcome */}
|
||
{currentStep === 1 && (
|
||
<div className="space-y-6">
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 bg-indigo-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||
<Rocket className="w-10 h-10 text-indigo-600" aria-hidden="true" />
|
||
</div>
|
||
<h3 className="text-2xl font-bold text-slate-900 mb-4">
|
||
Benvenuto su LogWhisperer AI
|
||
</h3>
|
||
<p className="text-slate-600 mb-6 max-w-lg mx-auto">
|
||
Il tuo assistente DevOps personale che monitora i log del server
|
||
e ti avvisa immediatamente quando qualcosa va storto, suggerendoti
|
||
il comando esatto per risolvere il problema.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="bg-slate-50 rounded-xl p-6">
|
||
<h4 className="font-semibold text-slate-900 mb-4 flex items-center gap-2">
|
||
<AlertCircle className="w-5 h-5 text-indigo-600" />
|
||
Cosa succederà:
|
||
</h4>
|
||
<ul className="space-y-3 text-slate-600">
|
||
<li className="flex items-start gap-3">
|
||
<span className="text-indigo-600 font-bold">1.</span>
|
||
<span>Genereremo un webhook URL univoco per il tuo account</span>
|
||
</li>
|
||
<li className="flex items-start gap-3">
|
||
<span className="text-indigo-600 font-bold">2.</span>
|
||
<span>Installerai uno script leggero sul tuo server</span>
|
||
</li>
|
||
<li className="flex items-start gap-3">
|
||
<span className="text-indigo-600 font-bold">3.</span>
|
||
<span>Inizierai a ricevere notifiche intelligenti su Telegram</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="text-center text-sm text-slate-500">
|
||
<p>⏱️ Tempo stimato: 5 minuti</p>
|
||
<p>💰 Nessuna carta di credito richiesta</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Step 2: Webhook Generation */}
|
||
{currentStep === 2 && (
|
||
<div className="space-y-6">
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 bg-indigo-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||
<Settings className="w-10 h-10 text-indigo-600" aria-hidden="true" />
|
||
</div>
|
||
<h3 className="text-2xl font-bold text-slate-900 mb-4">
|
||
Genera il tuo Webhook
|
||
</h3>
|
||
<p className="text-slate-600 mb-6 max-w-lg mx-auto">
|
||
Clicca il pulsante qui sotto per generare il tuo webhook URL univoco.
|
||
Questo URL riceverà i log dal tuo server.
|
||
</p>
|
||
</div>
|
||
|
||
{!webhookUrl ? (
|
||
<div className="text-center">
|
||
<button
|
||
onClick={generateWebhook}
|
||
disabled={isGenerating}
|
||
className="inline-flex items-center gap-2 px-8 py-4 bg-indigo-600 hover:bg-indigo-700 disabled:bg-indigo-400 text-white font-bold rounded-xl transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||
>
|
||
{isGenerating ? (
|
||
<>
|
||
<RefreshCw className="w-5 h-5 animate-spin" />
|
||
Generazione...
|
||
</>
|
||
) : (
|
||
<>
|
||
<Rocket className="w-5 h-5" />
|
||
Genera Webhook URL
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
<div className="bg-slate-900 rounded-xl p-4">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<span className="text-slate-400 text-sm">Il tuo webhook URL:</span>
|
||
<button
|
||
onClick={copyWebhook}
|
||
className="flex items-center gap-1 text-indigo-400 hover:text-indigo-300 text-sm"
|
||
>
|
||
{copied ? (
|
||
<><Check className="w-4 h-4" /> Copiato!</>
|
||
) : (
|
||
<><Copy className="w-4 h-4" /> Copia</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
<code className="text-green-400 font-mono text-sm break-all">
|
||
{webhookUrl}
|
||
</code>
|
||
</div>
|
||
|
||
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<p className="text-green-800 text-sm flex items-center gap-2">
|
||
<Check className="w-5 h-5" />
|
||
Webhook generato con successo! Procedi allo step successivo per installare lo script.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Step 3: Setup Instructions */}
|
||
{currentStep === 3 && (
|
||
<div className="space-y-6">
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 bg-indigo-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||
<Terminal className="w-10 h-10 text-indigo-600" aria-hidden="true" />
|
||
</div>
|
||
<h3 className="text-2xl font-bold text-slate-900 mb-4">
|
||
Installa sul tuo Server
|
||
</h3>
|
||
<p className="text-slate-600 mb-6 max-w-lg mx-auto">
|
||
Esegui questo comando sul tuo server per installare LogWhisperer.
|
||
Lo script configurerà automaticamente il monitoraggio dei log.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="bg-slate-900 rounded-xl p-6">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<span className="text-slate-400 text-sm">Comando di installazione:</span>
|
||
<button
|
||
onClick={copyCurlCommand}
|
||
className="flex items-center gap-1 text-indigo-400 hover:text-indigo-300 text-sm"
|
||
>
|
||
{copied ? (
|
||
<><Check className="w-4 h-4" /> Copiato!</>
|
||
) : (
|
||
<><Copy className="w-4 h-4" /> Copia comando</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
<code className="text-green-400 font-mono text-sm block break-all">
|
||
{getInstallCommand()}
|
||
</code>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<h4 className="font-semibold text-slate-900">Istruzioni:</h4>
|
||
<ol className="space-y-3 text-slate-600">
|
||
<li className="flex items-start gap-3">
|
||
<span className="w-6 h-6 bg-indigo-100 text-indigo-600 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0">1</span>
|
||
<span>Connettiti al tuo server via SSH</span>
|
||
</li>
|
||
<li className="flex items-start gap-3">
|
||
<span className="w-6 h-6 bg-indigo-100 text-indigo-600 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0">2</span>
|
||
<span>Esegui il comando copiato sopra</span>
|
||
</li>
|
||
<li className="flex items-start gap-3">
|
||
<span className="w-6 h-6 bg-indigo-100 text-indigo-600 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0">3</span>
|
||
<span>Segui la configurazione guidata</span>
|
||
</li>
|
||
<li className="flex items-start gap-3">
|
||
<span className="w-6 h-6 bg-indigo-100 text-indigo-600 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0">4</span>
|
||
<span>Controlla Telegram per il messaggio di conferma</span>
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||
<p className="text-blue-800 text-sm">
|
||
<strong>Nota:</strong> Lo script richiede privilegi sudo per installare il servizio di sistema.
|
||
Nessun dato sensibile viene inviato durante l'installazione.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Navigation Buttons */}
|
||
<div className="flex items-center justify-between mt-8 pt-8 border-t border-slate-200">
|
||
<button
|
||
onClick={prevStep}
|
||
disabled={currentStep === 1}
|
||
className="flex items-center gap-2 px-6 py-3 text-slate-600 hover:text-slate-900 disabled:opacity-50 disabled:cursor-not-allowed font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 rounded-lg"
|
||
>
|
||
<ChevronLeft className="w-5 h-5" />
|
||
Indietro
|
||
</button>
|
||
|
||
<button
|
||
onClick={nextStep}
|
||
disabled={currentStep === 2 && !webhookUrl}
|
||
className="flex items-center gap-2 px-8 py-3 bg-indigo-600 hover:bg-indigo-700 disabled:bg-slate-300 text-white font-bold rounded-xl transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||
>
|
||
{currentStep === 3 ? (
|
||
<>
|
||
<Check className="w-5 h-5" />
|
||
Completa
|
||
</>
|
||
) : (
|
||
<>
|
||
Avanti
|
||
<ChevronRight className="w-5 h-5" />
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
};
|
||
|
||
export default OnboardingWizard; |