This commit is contained in:
18
app/index.css
Normal file
18
app/index.css
Normal file
@@ -0,0 +1,18 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
10
app/main.jsx
Normal file
10
app/main.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './wizard.jsx';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
563
app/wizard.jsx
Normal file
563
app/wizard.jsx
Normal file
@@ -0,0 +1,563 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
User,
|
||||
Server,
|
||||
Bot,
|
||||
ShieldCheck,
|
||||
Wrench,
|
||||
FileText,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
Send,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
Copy
|
||||
} from 'lucide-react';
|
||||
|
||||
// --- CONFIGURAZIONE API ---
|
||||
const API_ENDPOINT = "https://api.example.com/v1/requirements";
|
||||
const API_KEY_HEADER = "X-Api-Key"; // Se necessario
|
||||
const API_KEY_VALUE = ""; // Inserire chiave se necessaria
|
||||
|
||||
export default function App() {
|
||||
const [step, setStep] = useState(0);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submitStatus, setSubmitStatus] = useState(null); // 'success', 'error'
|
||||
const [payloadPreview, setPayloadPreview] = useState("");
|
||||
|
||||
// Stato unico per tutti i dati del form
|
||||
const [formData, setFormData] = useState({
|
||||
// Metadata
|
||||
commerciale: '',
|
||||
cliente: '',
|
||||
data: new Date().toISOString().split('T')[0],
|
||||
|
||||
// Step 1: Gestionale
|
||||
gestionale_obiettivo: '',
|
||||
gestionale_utenti_interni: '',
|
||||
gestionale_utenti_esterni: false,
|
||||
gestionale_device: 'desktop', // desktop, web, app
|
||||
gestionale_entita: '',
|
||||
gestionale_migrazione: 'nessuna', // nessuna, excel, legacy
|
||||
|
||||
// Step 2: Automazioni
|
||||
automazione_trigger: '',
|
||||
automazione_azione: '',
|
||||
automazione_software_a: '',
|
||||
automazione_software_b: '',
|
||||
automazione_frequenza: 'bassa',
|
||||
|
||||
// Step 3: Compliance
|
||||
compliance_dati: [], // array di stringhe
|
||||
compliance_retention: '',
|
||||
compliance_hosting: 'cloud',
|
||||
|
||||
// Step 4: Manutenzione
|
||||
manutenzione_tipo: 'standard',
|
||||
manutenzione_budget: '',
|
||||
manutenzione_deadline: '',
|
||||
|
||||
// Step 5: Extra
|
||||
note_finali: ''
|
||||
});
|
||||
|
||||
// Gestione input generica
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
|
||||
if (type === 'checkbox' && name === 'compliance_dati') {
|
||||
// Gestione array per checkbox multiple
|
||||
let updatedList = [...formData.compliance_dati];
|
||||
if (checked) {
|
||||
updatedList.push(value);
|
||||
} else {
|
||||
updatedList = updatedList.filter((item) => item !== value);
|
||||
}
|
||||
setFormData({ ...formData, compliance_dati: updatedList });
|
||||
} else if (type === 'checkbox') {
|
||||
setFormData({ ...formData, [name]: checked });
|
||||
} else {
|
||||
setFormData({ ...formData, [name]: value });
|
||||
}
|
||||
};
|
||||
|
||||
// Navigazione
|
||||
const nextStep = () => setStep((prev) => Math.min(prev + 1, steps.length - 1));
|
||||
const prevStep = () => setStep((prev) => Math.max(prev - 1, 0));
|
||||
|
||||
// Invio Dati
|
||||
const handleSubmit = async () => {
|
||||
setIsSubmitting(true);
|
||||
const payload = JSON.stringify(formData, null, 2);
|
||||
setPayloadPreview(payload);
|
||||
|
||||
try {
|
||||
// Simulazione o Invio Reale
|
||||
const response = await fetch(API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
[API_KEY_HEADER]: API_KEY_VALUE
|
||||
},
|
||||
body: payload
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setSubmitStatus('success');
|
||||
} else {
|
||||
// Fallback per demo (visto che example.com darà 404/405)
|
||||
console.warn("API Error (Expected in demo):", response.status);
|
||||
setSubmitStatus('error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Network Error:", error);
|
||||
setSubmitStatus('error');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
nextStep(); // Vai alla schermata finale
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(payloadPreview);
|
||||
alert("Dati copiati negli appunti!");
|
||||
};
|
||||
|
||||
// Definizione degli Step
|
||||
const steps = [
|
||||
{ id: 0, title: "Start", icon: User, color: "bg-blue-500" },
|
||||
{ id: 1, title: "Gestionale", icon: Server, color: "bg-indigo-500" },
|
||||
{ id: 2, title: "Automazione", icon: Bot, color: "bg-purple-500" },
|
||||
{ id: 3, title: "GDPR", icon: ShieldCheck, color: "bg-red-500" },
|
||||
{ id: 4, title: "Service", icon: Wrench, color: "bg-orange-500" },
|
||||
{ id: 5, title: "Note", icon: FileText, color: "bg-gray-500" },
|
||||
{ id: 6, title: "Fine", icon: Send, color: "bg-green-500" },
|
||||
];
|
||||
|
||||
const currentStepData = steps[step];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 font-sans text-slate-800 flex flex-col">
|
||||
|
||||
{/* HEADER / PROGRESS BAR */}
|
||||
<div className="bg-white shadow-sm sticky top-0 z-10">
|
||||
<div className="max-w-lg mx-auto px-4 py-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs font-bold text-gray-400 uppercase tracking-wider">
|
||||
Step {step + 1} di {steps.length}
|
||||
</span>
|
||||
<span className="text-xs font-semibold text-blue-600">
|
||||
{currentStepData.title}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 w-full bg-gray-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full transition-all duration-300 ease-out ${currentStepData.color}`}
|
||||
style={{ width: `${((step + 1) / steps.length) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CONTENT AREA */}
|
||||
<div className="flex-1 max-w-lg mx-auto w-full p-4 pb-24 overflow-y-auto">
|
||||
|
||||
{/* Titolo Dinamico */}
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<div className={`p-3 rounded-xl text-white shadow-lg ${currentStepData.color}`}>
|
||||
<currentStepData.icon size={24} />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
{currentStepData.title === "Start" && "Anagrafica"}
|
||||
{currentStepData.title === "Gestionale" && "Sviluppo App"}
|
||||
{currentStepData.title === "Automazione" && "Bot & Integrazioni"}
|
||||
{currentStepData.title === "GDPR" && "Privacy & Dati"}
|
||||
{currentStepData.title === "Service" && "Manutenzione"}
|
||||
{currentStepData.title === "Note" && "Info Extra"}
|
||||
{currentStepData.title === "Fine" && "Riepilogo"}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* STEP 0: ANAGRAFICA */}
|
||||
{step === 0 && (
|
||||
<div className="space-y-4 animate-fadeIn">
|
||||
<InputGroup label="Riferimento Commerciale" icon={User}>
|
||||
<input
|
||||
type="text"
|
||||
name="commerciale"
|
||||
value={formData.commerciale}
|
||||
onChange={handleChange}
|
||||
placeholder="Il tuo nome"
|
||||
className="w-full p-3 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup label="Nome Cliente / Azienda" icon={User}>
|
||||
<input
|
||||
type="text"
|
||||
name="cliente"
|
||||
value={formData.cliente}
|
||||
onChange={handleChange}
|
||||
placeholder="Es. Rossi Srl"
|
||||
className="w-full p-3 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup label="Data Incontro">
|
||||
<input
|
||||
type="date"
|
||||
name="data"
|
||||
value={formData.data}
|
||||
onChange={handleChange}
|
||||
className="w-full p-3 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"
|
||||
/>
|
||||
</InputGroup>
|
||||
<div className="bg-blue-50 p-4 rounded-lg text-sm text-blue-800 border border-blue-100 mt-4">
|
||||
💡 <strong>Info:</strong> Tutti i campi successivi sono facoltativi. Compila solo le sezioni pertinenti al progetto.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* STEP 1: GESTIONALE */}
|
||||
{step === 1 && (
|
||||
<div className="space-y-5 animate-fadeIn">
|
||||
<Label text="Obiettivo principale del software?" />
|
||||
<textarea
|
||||
name="gestionale_obiettivo"
|
||||
value={formData.gestionale_obiettivo}
|
||||
onChange={handleChange}
|
||||
placeholder="Es. Gestire il magazzino e le spedizioni..."
|
||||
className="w-full p-3 h-24 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none resize-none"
|
||||
></textarea>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label text="Utenti Interni" />
|
||||
<input
|
||||
type="number"
|
||||
name="gestionale_utenti_interni"
|
||||
value={formData.gestionale_utenti_interni}
|
||||
onChange={handleChange}
|
||||
placeholder="N."
|
||||
className="w-full p-3 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label text="Utenti Esterni?" />
|
||||
<div className="flex items-center h-full">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="gestionale_utenti_esterni"
|
||||
checked={formData.gestionale_utenti_esterni}
|
||||
onChange={handleChange}
|
||||
className="w-5 h-5 accent-indigo-600"
|
||||
/>
|
||||
<span className="text-gray-700">Sì, accedono</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Label text="Tipo di Dispositivo richiesto" />
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<SelectCard
|
||||
selected={formData.gestionale_device === 'desktop'}
|
||||
onClick={() => setFormData({...formData, gestionale_device: 'desktop'})}
|
||||
label="Solo PC"
|
||||
/>
|
||||
<SelectCard
|
||||
selected={formData.gestionale_device === 'web'}
|
||||
onClick={() => setFormData({...formData, gestionale_device: 'web'})}
|
||||
label="Web Resp."
|
||||
/>
|
||||
<SelectCard
|
||||
selected={formData.gestionale_device === 'app'}
|
||||
onClick={() => setFormData({...formData, gestionale_device: 'app'})}
|
||||
label="App Nativa"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Label text="Migrazione Dati Attuali" />
|
||||
<select
|
||||
name="gestionale_migrazione"
|
||||
value={formData.gestionale_migrazione}
|
||||
onChange={handleChange}
|
||||
className="w-full p-3 bg-white border rounded-lg"
|
||||
>
|
||||
<option value="nessuna">Nessuna (Start from scratch)</option>
|
||||
<option value="carta">Cartaceo / Mentale</option>
|
||||
<option value="excel">Excel / Fogli Google</option>
|
||||
<option value="legacy">Vecchio Gestionale (SQL/CSV)</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* STEP 2: AUTOMAZIONE */}
|
||||
{step === 2 && (
|
||||
<div className="space-y-5 animate-fadeIn">
|
||||
<div>
|
||||
<Label text="Trigger (Quando parte?)" />
|
||||
<input
|
||||
type="text"
|
||||
name="automazione_trigger"
|
||||
value={formData.automazione_trigger}
|
||||
onChange={handleChange}
|
||||
placeholder="Es. Arrivo email, compilazione form..."
|
||||
className="w-full p-3 border rounded-lg focus:ring-2 focus:ring-purple-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label text="Azione (Cosa fa?)" />
|
||||
<textarea
|
||||
name="automazione_azione"
|
||||
value={formData.automazione_azione}
|
||||
onChange={handleChange}
|
||||
placeholder="Es. Crea fattura e salvala in Drive..."
|
||||
className="w-full p-3 h-20 border rounded-lg focus:ring-2 focus:ring-purple-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-purple-50 rounded-lg border border-purple-100">
|
||||
<Label text="Software Coinvolti" />
|
||||
<div className="space-y-3 mt-2">
|
||||
<input
|
||||
type="text"
|
||||
name="automazione_software_a"
|
||||
value={formData.automazione_software_a}
|
||||
onChange={handleChange}
|
||||
placeholder="Software A (es. Gmail)"
|
||||
className="w-full p-2 border rounded bg-white"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="automazione_software_b"
|
||||
value={formData.automazione_software_b}
|
||||
onChange={handleChange}
|
||||
placeholder="Software B (es. Zucchetti)"
|
||||
className="w-full p-2 border rounded bg-white"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-purple-600 mt-2 italic">Chiedi sempre se hanno accesso API o admin.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* STEP 3: GDPR */}
|
||||
{step === 3 && (
|
||||
<div className="space-y-5 animate-fadeIn">
|
||||
<Label text="Tipologia Dati Trattati (GDPR)" />
|
||||
<div className="space-y-2">
|
||||
{['Dati Comuni (Anagrafiche)', 'Dati Finanziari (IBAN/CC)', 'Dati Sensibili (Sanitari/Giudiziari)'].map((tipo) => (
|
||||
<label key={tipo} className="flex items-center gap-3 p-3 border rounded-lg bg-white active:bg-gray-50">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="compliance_dati"
|
||||
value={tipo}
|
||||
checked={formData.compliance_dati.includes(tipo)}
|
||||
onChange={handleChange}
|
||||
className="w-5 h-5 accent-red-600"
|
||||
/>
|
||||
<span className="text-sm font-medium text-gray-700">{tipo}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Label text="Hosting Preferito" />
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<SelectCard
|
||||
selected={formData.compliance_hosting === 'cloud'}
|
||||
onClick={() => setFormData({...formData, compliance_hosting: 'cloud'})}
|
||||
label="Cloud (SaaS)"
|
||||
sub="Gestito da noi"
|
||||
/>
|
||||
<SelectCard
|
||||
selected={formData.compliance_hosting === 'premise'}
|
||||
onClick={() => setFormData({...formData, compliance_hosting: 'premise'})}
|
||||
label="On-Premise"
|
||||
sub="Server Cliente"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* STEP 4: MANUTENZIONE */}
|
||||
{step === 4 && (
|
||||
<div className="space-y-5 animate-fadeIn">
|
||||
<Label text="Livello Service Level Agreement (SLA)" />
|
||||
<select
|
||||
name="manutenzione_tipo"
|
||||
value={formData.manutenzione_tipo}
|
||||
onChange={handleChange}
|
||||
className="w-full p-3 bg-white border rounded-lg mb-4"
|
||||
>
|
||||
<option value="standard">Standard (Sicurezza + Backup)</option>
|
||||
<option value="business">Business (Monitoraggio Automazioni)</option>
|
||||
<option value="critical">Critical (H24 - Costo alto)</option>
|
||||
</select>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label text="Budget Stimato" />
|
||||
<input
|
||||
type="text"
|
||||
name="manutenzione_budget"
|
||||
value={formData.manutenzione_budget}
|
||||
onChange={handleChange}
|
||||
placeholder="€ Range"
|
||||
className="w-full p-3 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label text="Deadline" />
|
||||
<input
|
||||
type="date"
|
||||
name="manutenzione_deadline"
|
||||
value={formData.manutenzione_deadline}
|
||||
onChange={handleChange}
|
||||
className="w-full p-3 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* STEP 5: NOTE FINALI */}
|
||||
{step === 5 && (
|
||||
<div className="space-y-4 animate-fadeIn">
|
||||
<div className="bg-yellow-50 border border-yellow-200 p-4 rounded-lg flex gap-3 items-start">
|
||||
<AlertCircle className="text-yellow-600 shrink-0" size={20} />
|
||||
<p className="text-sm text-yellow-800">
|
||||
Usa questo spazio per dettagli tecnici non coperti dal wizard, sensazioni sul cliente o vincoli particolari.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
name="note_finali"
|
||||
value={formData.note_finali}
|
||||
onChange={handleChange}
|
||||
placeholder="Scrivi qui note libere..."
|
||||
className="w-full p-4 h-64 bg-white border border-gray-300 rounded-lg focus:ring-2 focus:ring-gray-500 outline-none resize-none text-lg"
|
||||
></textarea>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* STEP 6: ESITO INVIO */}
|
||||
{step === 6 && (
|
||||
<div className="text-center space-y-6 animate-fadeIn pt-10">
|
||||
{submitStatus === 'success' ? (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<CheckCircle className="text-green-600" size={40} />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-green-700">Inviato con Successo!</h2>
|
||||
<p className="text-gray-600">Il team tecnico ha ricevuto i requisiti.</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-8 text-blue-600 underline font-medium"
|
||||
>
|
||||
Nuova Raccolta
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="w-20 h-20 bg-orange-100 rounded-full flex items-center justify-center">
|
||||
<AlertCircle className="text-orange-600" size={40} />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-gray-800">Invio API Fallito</h2>
|
||||
<p className="text-sm text-gray-500 px-4">
|
||||
L'API non è raggiungibile (normale in questa demo). <br/>
|
||||
Copia il report qui sotto e invialo manualmente.
|
||||
</p>
|
||||
|
||||
<div className="w-full bg-gray-800 rounded-lg p-4 text-left relative group">
|
||||
<pre className="text-green-400 text-xs overflow-x-auto font-mono whitespace-pre-wrap break-all">
|
||||
{payloadPreview}
|
||||
</pre>
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="absolute top-2 right-2 bg-white/20 hover:bg-white/40 text-white p-2 rounded-md backdrop-blur-sm transition"
|
||||
>
|
||||
<Copy size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="w-full bg-gray-900 text-white py-3 px-6 rounded-xl font-semibold flex items-center justify-center gap-2"
|
||||
>
|
||||
<Copy size={18} /> Copia JSON
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* FOOTER NAVIGATION */}
|
||||
{step < 6 && (
|
||||
<div className="bg-white border-t border-gray-200 p-4 fixed bottom-0 w-full max-w-lg mx-auto left-0 right-0 z-20">
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={prevStep}
|
||||
disabled={step === 0}
|
||||
className={`p-4 rounded-xl flex items-center justify-center transition-colors ${step === 0 ? 'text-gray-300 bg-gray-50' : 'text-gray-600 bg-gray-100 hover:bg-gray-200'}`}
|
||||
>
|
||||
<ArrowLeft size={24} />
|
||||
</button>
|
||||
|
||||
{step === 5 ? (
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 bg-green-600 text-white font-bold text-lg rounded-xl shadow-lg shadow-green-200 hover:bg-green-700 active:scale-95 transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
{isSubmitting ? 'Invio...' : 'Invia al Tecnico'} <Send size={20} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={nextStep}
|
||||
className="flex-1 bg-blue-600 text-white font-bold text-lg rounded-xl shadow-lg shadow-blue-200 hover:bg-blue-700 active:scale-95 transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
Avanti <ArrowRight size={24} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --- UI COMPONENTS ---
|
||||
|
||||
function InputGroup({ label, icon: Icon, children }) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-gray-700 font-semibold text-sm mb-1">
|
||||
{Icon && <Icon size={16} className="text-gray-400" />}
|
||||
{label}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Label({ text }) {
|
||||
return <h3 className="text-gray-800 font-semibold text-sm mb-2">{text}</h3>;
|
||||
}
|
||||
|
||||
function SelectCard({ selected, onClick, label, sub }) {
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={`p-3 rounded-lg border-2 cursor-pointer transition-all text-center ${
|
||||
selected
|
||||
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||||
: 'border-gray-200 bg-white text-gray-600 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="font-bold text-sm">{label}</div>
|
||||
{sub && <div className="text-xs opacity-75">{sub}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user