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:
354
frontend/src/components/sections/OnboardingWizard.tsx
Normal file
354
frontend/src/components/sections/OnboardingWizard.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user