import React, { useState } from "react";
// MultiStepFormWithProgressAndAccordion.jsx
// Single-file React component (TailwindCSS required in host project)
// Features:
// - 3-step form with validation
// - Top progress bar (percentage)
// - After final submit, display an accordion for each step
// that shows: submitted values, step completion percentage, and backlink field
// - Uses Tailwind classes for styling and framer-motion for subtle animations (optional)
export default function MultiStepFormWithProgressAndAccordion() {
const steps = [
{
id: 1,
title: "Your Info",
fields: [
{ name: "name", label: "Name", placeholder: "Your full name" },
{ name: "email", label: "Email", placeholder: "[email protected]" },
],
},
{
id: 2,
title: "Website / Backlink",
fields: [
{ name: "website", label: "Website URL", placeholder: "https://example.com" },
{ name: "anchor", label: "Anchor Text", placeholder: "Example Anchor" },
],
},
{
id: 3,
title: "Answer & Tags",
fields: [
{ name: "answer", label: "Answer (short)", placeholder: "Write your answer here...", type: "textarea" },
{ name: "tags", label: "Tags (comma)", placeholder: "tag1, tag2" },
],
},
];
// initialize form data
const initialData = {};
steps.forEach((s) => s.fields.forEach((f) => (initialData[f.name] = "")));
const [currentStep, setCurrentStep] = useState(0);
const [formData, setFormData] = useState(initialData);
const [submitted, setSubmitted] = useState(false);
const [expanded, setExpanded] = useState({});
function handleChange(e) {
const { name, value } = e.target;
setFormData((p) => ({ ...p, [name]: value }));
}
function stepCompletionPercent(stepIndex) {
const fields = steps[stepIndex].fields;
const total = fields.length;
let filled = 0;
fields.forEach((f) => {
const v = (formData[f.name] || "").toString().trim();
if (v.length > 0) filled += 1;
});
return Math.round((filled / total) * 100);
}
function overallProgressPercent() {
const totalFields = steps.reduce((acc, s) => acc + s.fields.length, 0);
const filled = Object.values(formData).filter((v) => (v || "").toString().trim().length > 0).length;
return Math.round((filled / totalFields) * 100);
}
function nextStep() {
if (currentStep < steps.length - 1) setCurrentStep((s) => s + 1);
}
function prevStep() {
if (currentStep > 0) setCurrentStep((s) => s - 1);
}
function validateStep(index) {
// simple required check for demonstration
const fields = steps[index].fields;
for (const f of fields) {
if (!formData[f.name] || formData[f.name].toString().trim() === "") return false;
}
return true;
}
function handleSubmit(e) {
e.preventDefault();
// Validate all steps
for (let i = 0; i < steps.length; i++) {
if (!validateStep(i)) {
setCurrentStep(i);
alert(`Please complete "${steps[i].title}" before submitting.`);
return;
}
}
// Simulate submission (e.g., POST to your API)
// For this component we mark as submitted and show accordions with percentages per step
setSubmitted(true);
// expand all accordions by default after submit
const ex = {};
steps.forEach((s) => (ex[s.id] = true));
setExpanded(ex);
}
function toggleAccordion(stepId) {
setExpanded((p) => ({ ...p, [stepId]: !p[stepId] }));
}
return (
<div className="max-w-3xl mx-auto p-4">
<h2 className="text-2xl font-semibold mb-4">Multi-step Answer Submission (Backlink)</h2>
{/* Progress bar */}
<div className="mb-4">
<div className="flex items-center justify-between text-sm mb-1">
<span>Progress</span>
<span className="font-medium">{overallProgressPercent()}%</span>
</div>
<div className="bg-gray-200 rounded-full h-3 overflow-hidden">
<div
className="h-3 rounded-full transition-all duration-500"
style={{ width: `${overallProgressPercent()}%`, background: "linear-gradient(90deg,#4f46e5,#06b6d4)" }}
/>
</div>
</div>
{!submitted ? (
<form onSubmit={handleSubmit} className="bg-white rounded-lg shadow p-6">
<div className="mb-6">
<div className="flex gap-2 items-center text-sm">
{steps.map((s, idx) => (
<div key={s.id} className={`flex-1 text-center p-2 rounded ${idx === currentStep ? "bg-indigo-50" : ""}`}>
<div className="font-medium">Step {idx + 1}</div>
<div className="text-xs text-gray-500">{s.title}</div>
<div className="mt-1 text-xs">{stepCompletionPercent(idx)}%</div>
</div>
))}
</div>
</div>
<div>
<h3 className="font-semibold mb-3">{steps[currentStep].title}</h3>
<div className="space-y-4">
{steps[currentStep].fields.map((f) => (
<div key={f.name}>
<label className="block text-sm font-medium mb-1">{f.label}</label>
{f.type === "textarea" ? (
<textarea
name={f.name}
rows={4}
placeholder={f.placeholder}
value={formData[f.name]}
onChange={handleChange}
className="w-full border rounded p-2"
/>
) : (
<input
name={f.name}
placeholder={f.placeholder}
value={formData[f.name]}
onChange={handleChange}
className="w-full border rounded p-2"
/>
)}
</div>
))}
</div>
</div>
<div className="mt-6 flex justify-between">
<div>
<button type="button" onClick={prevStep} disabled={currentStep === 0} className="px-4 py-2 rounded-md border">
Back
</button>
</div>
<div className="flex gap-2">
{currentStep < steps.length - 1 ? (
<button
type="button"
onClick={() => {
if (validateStep(currentStep)) nextStep();
else alert("Please fill required fields in this step.");
}}
className="px-4 py-2 rounded-md bg-indigo-600 text-white"
>
Next
</button>
) : (
<button type="submit" className="px-4 py-2 rounded-md bg-green-600 text-white">
Submit Answer & Create Backlink
</button>
)}
</div>
</div>
</form>
) : (
<div className="space-y-4">
<div className="bg-white rounded-lg shadow p-4">
<div className="flex items-center justify-between mb-2">
<div>
<h3 className="font-semibold">Submission Complete</h3>
<p className="text-sm text-gray-600">Your answer has been submitted. Below are details by step and completion percentage.</p>
</div>
<div className="text-right">
<div className="text-sm">Overall: <span className="font-medium">{overallProgressPercent()}%</span></div>
</div>
</div>
</div>
{/* Accordions for each step */}
{steps.map((s, idx) => (
<div key={s.id} className="bg-white rounded-lg shadow">
<button
type="button"
onClick={() => toggleAccordion(s.id)}
className="w-full text-left p-4 flex items-center justify-between"
>
<div>
<div className="font-medium">{s.title}</div>
<div className="text-xs text-gray-500">Step {idx + 1} • {stepCompletionPercent(idx)}% complete</div>
</div>
<div className="text-sm">{expanded[s.id] ? "−" : "+"}</div>
</button>
{expanded[s.id] && (
<div className="p-4 border-t">
<div className="grid gap-3">
{s.fields.map((f) => (
<div key={f.name} className="">
<div className="text-xs text-gray-500">{f.label}</div>
<div className="mt-1 break-words">{formData[f.name] || <span className="text-gray-400">(empty)</span>}</div>
</div>
))}
<div className="pt-2">
<div className="text-xs text-gray-500">Step progress</div>
<div className="mt-1 w-full bg-gray-100 rounded-full h-2 overflow-hidden">
<div style={{ width: `${stepCompletionPercent(idx)}%` }} className="h-2 rounded-full transition-all" />
</div>
</div>
{/* Quick actions: copy backlink, open website */}
{s.fields.find((ff) => ff.name === "website") && (
<div className="flex gap-2 mt-3">
<a
href={formData.website || "#"}
target="_blank"
rel="noreferrer"
className="px-3 py-2 rounded border text-sm"
>
Open Link
</a>
<button
type="button"
onClick={() => navigator.clipboard && navigator.clipboard.writeText(formData.website || "")}
className="px-3 py-2 rounded border text-sm"
>
Copy URL
</button>
</div>
)}
</div>
</div>
)}
</div>
))}
<div className="flex gap-2 mt-4">
<button
className="px-4 py-2 rounded border"
onClick={() => {
// reset to start a new submission
setFormData(initialData);
setSubmitted(false);
setCurrentStep(0);
setExpanded({});
}}
>
Submit Another
</button>
<button
className="px-4 py-2 rounded bg-indigo-600 text-white"
onClick={() => alert("Implement real API POST in handleSubmit to actually create backlinks on your site.")}
>
Integrate with API
</button>
</div>
</div>
)}
<div className="mt-6 text-xs text-gray-500">Tip: Hook handleSubmit to your backend (fetch/axios) to actually persist answers and create backlinks on your site.</div>
</div>
);
}
For More Information