/* global React */
/* Step renderers — one per step of the wizard */
function StepCategory({ state, update }) {
return (
{STEP_KICKER.category}
What are you selling?
Pick the category that fits best — we'll only ask questions relevant to it.
{CATEGORIES.map(c => (
{
if (state.category !== id) {
update({ ...defaultState(), category: id, sellerName: state.sellerName, sellerPhone: state.sellerPhone, sellerEmail: state.sellerEmail });
} else {
update({ category: id });
}
}}
/>
))}
);
}
function StepType({ state, update }) {
const freeText = state.category === "accessories" || state.category === "technology";
const types = TYPES_BY_CATEGORY[state.category];
return (
{STEP_KICKER.type}
{freeText ? "Describe the item" : "Pick the type"}
{freeText
? "A short, searchable name. Think what buyers would scroll past looking for."
: "This helps us ask the right follow-up questions about size and fit."}
{freeText ? (
update({ type: v })} placeholder="Type or paste here" />
) : (
<>
{types.map(t => (
update({ type: id })} />
))}
{state.category === "luggage" && state.type === "other" && (
update({ customType: v })}
placeholder="e.g. Hydration pack, seat bag, rain cover…" />
)}
>
)}
);
}
function StepSize({ state, update }) {
const isMotorcycle = state.category === "motorcycle";
const isLuggage = state.category === "luggage";
const isBoots = state.category === "riding-gears" && state.type === "boots";
const sizes = getSizeOptions(state.category, state.type, state.sizeSystem);
return (
{STEP_KICKER.size}
{isMotorcycle ? "Which engine band?" : isLuggage ? "What's the capacity?" : "What size is it?"}
{isMotorcycle ? "We group motorcycles by CC band so buyers can filter easily."
: isLuggage ? "Give the litre capacity — that's what buyers search by."
: "Match what's printed on the tag — mismatched sizes are the #1 reason deals fall through."}
{isLuggage ? (
update({ size: v })}
type="number" placeholder="20" prefix="L" />
) : isBoots ? (
<>
update({ sizeSystem: v })}
options={["UK","US","EU"]} />
update({ size: v })} options={sizes} />
>
) : (
update({ size: v })} options={sizes} />
)}
);
}
function StepDetails({ state, update }) {
const isMotorcycle = state.category === "motorcycle";
const isGear = state.category === "riding-gears";
const isHelmet = isGear && state.type === "helmet";
const isJacketPant = isGear && (state.type === "jacket" || state.type === "pant");
const isGloves = isGear && state.type === "gloves";
const isBoots = isGear && state.type === "boots";
const isAccTech = state.category === "accessories" || state.category === "technology";
const isLuggage = state.category === "luggage";
return (
{STEP_KICKER.details}
The details that matter
The more specific you are here, the fewer follow-up messages you'll field.
{/* Brand + model */}
Brand & model
{isMotorcycle
? update({ brand: v })}
options={MOTORCYCLE_BRANDS} placeholder="Select brand" />
: update({ brand: v })}
placeholder="e.g. Alpinestars, Rynox, Shoei" />}
update({ model: v })}
placeholder="e.g. GT-Air II" />
{isMotorcycle && (
update({ variant: v })}
placeholder="e.g. Metallic Silver" />
)}
update({ condition: v })}
options={CONDITIONS} placeholder="How is it?" />
{/* Motorcycle specifics */}
{isMotorcycle && (
Motorcycle specifics
update({ mfgYear: v })} options={YEARS} placeholder="Select year" />
update({ regYear: v })} options={YEARS} placeholder="Select year" />
update({ odometer: v })}
type="number" placeholder="25000" prefix="km" />
update({ owners: v })} options={OWNER_COUNTS} placeholder="Select" />
update({ fuelType: v })} options={FUEL_TYPES} placeholder="Select" />
update({ purchaseYear: v })} options={YEARS} placeholder="Select" />
)}
{/* Helmet specifics */}
{isHelmet && (
Helmet specifics
update({ shell: v })} options={HELMET_SHELL} placeholder="Select" />
update({ bluetooth: v })} options={["Yes","No"]} />
update({ safetyRatings: v })} options={SAFETY_RATINGS} />
update({ visor: v })} options={VISOR_TYPES} />
)}
{/* Jacket / pants */}
{isJacketPant && (
Fabric & protection
update({ material: v })} options={MATERIALS} />
update({ protectionLevel: v })} options={["Level 1","Level 2"]} />
update({ waterproof: v })} options={["Yes","No"]} />
)}
{/* Gloves */}
{isGloves && (
Glove specifics
update({ gloveType: v })} options={GLOVE_TYPES} placeholder="Select" />
update({ material: v })} options={MATERIALS} placeholder="Select" />
update({ protectionLevel: v })} options={["Level 1","Level 2"]} />
)}
{/* Boots */}
{isBoots && (
Boot specifics
update({ bootLength: v })}
options={["Ankle","Half length","Full length"]} placeholder="Select" />
update({ waterproof: v })} options={["Yes","No"]} />
)}
{/* Acc / tech / luggage colour + used */}
{(isAccTech || isLuggage) && (
Extra info
update({ colour: v })} placeholder="Black, red, silver…" />
update({ purchaseYear: v })} options={YEARS} placeholder="Select" />
)}
);
}
function StepPrice({ state, update }) {
return (
{STEP_KICKER.price}
Pricing & location
Pin where it is and what you want for it. Clear numbers get faster replies.
update({ price: v })}
type="number" placeholder="5000" prefix="₹" />
update({ priceType: v })}
options={[{ value: "fixed", label: "Fixed" },{ value: "negotiable", label: "Negotiable" }]} />
update({ location: v })}
placeholder="City · locality (e.g. Bengaluru, Indiranagar)" />
update({ timeline: v })}
options={TIMELINES.map(t => ({ id: t, label: t }))} />
);
}
function StepPhotos({ state, update }) {
const inputRef = React.useRef(null);
const [dragOver, setDragOver] = React.useState(false);
const [dragId, setDragId] = React.useState(null);
async function addFiles(fileList) {
const files = Array.from(fileList || []).slice(0, 2 - (state.photos?.length || 0));
const reads = await Promise.all(files.map(f => new Promise(res => {
const reader = new FileReader();
reader.onload = e => res({ id: Math.random().toString(36).slice(2), name: f.name, dataUrl: e.target.result });
reader.readAsDataURL(f);
})));
const combined = [...(state.photos || []), ...reads].slice(0, 2);
update({ photos: combined });
}
function remove(id) {
update({ photos: state.photos.filter(p => p.id !== id) });
}
function onDrop(e) {
e.preventDefault();
setDragOver(false);
if (e.dataTransfer.files?.length) addFiles(e.dataTransfer.files);
}
function reorder(from, to) {
if (from === to) return;
const arr = [...state.photos];
const [m] = arr.splice(from, 1);
arr.splice(to, 0, m);
update({ photos: arr });
}
return (
{STEP_KICKER.photos}
Add up to two photos
One wide shot plus a close-up of any wear or detail is usually enough. The first photo leads the post.
inputRef.current?.click()}
onDragOver={e => { e.preventDefault(); setDragOver(true); }}
onDragLeave={() => setDragOver(false)}
onDrop={onDrop}
>
Drop photos here or click to choose
PNG or JPG · up to 2 photos · drag to reorder
addFiles(e.target.files)} />
{state.photos?.length > 0 && (
{state.photos.map((p, i) => (
setDragId(p.id)}
onDragOver={e => e.preventDefault()}
onDrop={() => {
const from = state.photos.findIndex(x => x.id === dragId);
const to = i;
setDragId(null);
reorder(from, to);
}}
onDragEnd={() => setDragId(null)}
>
{i === 0 ? "Lead" : "Photo 2"}
{ e.stopPropagation(); remove(p.id); }} aria-label="Remove">×
Drag to reorder
))}
)}
);
}
function StepReason({ state, update }) {
return (
{STEP_KICKER.reason}
A few lines, in your own words
The human part. Buyers read this first after the specs — keep it honest and short.
);
}
function StepSeller({ state, update }) {
return (
{STEP_KICKER.seller}
How should buyers reach you?
Your Wheelsafar-linked details are prefilled. Tweak anything that's changed.
Your details
update({ sellerName: v })} placeholder="Full name" />
update({ sellerPhone: v })}
placeholder="+91 98xxx xxxxx" />
update({ sellerEmail: v })}
placeholder="you@example.com" />
Contact preference
update({ contactPref: v })}
options={CONTACT_PREFS} />
Confirm & consent
update({ consent: v })}
sub="Required before we can post to the WhatsApp group.">
I confirm that I own this item or am authorised to sell it, and I agree to have this listing posted in the Garage Sale WhatsApp group.
);
}
Object.assign(window, {
StepCategory, StepType, StepSize, StepDetails, StepPrice,
StepPhotos, StepReason, StepSeller,
});