feat: add Footer and OnboardingWizard components - Sprint 3 Complete

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
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-03 16:43:23 +02:00
parent 462a5a9383
commit eb24b86308
4 changed files with 614 additions and 6 deletions

View File

@@ -0,0 +1,354 @@
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;