Shipping policy
import { useState } from "react";
const FONTS = `@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700;900&family=DM+Sans:wght@300;400;500&display=swap');`;
const steps = [
{ id: "business", label: "Business", icon: "🏢" },
{ id: "domestic", label: "Domestic", icon: "📦" },
{ id: "international", label: "International", icon: "🌍" },
{ id: "returns", label: "Returns", icon: "↩️" },
{ id: "special", label: "Special Cases", icon: "⚡" },
];
const defaultData = {
businessName: "",
website: "",
email: "",
processingDays: "1-3",
cutoffTime: "2:00 PM EST",
standardDays: "5-7",
standardCost: "5.99",
expeditedDays: "2-3",
expeditedCost: "14.99",
overnightCost: "29.99",
freeShippingThreshold: "75",
offerFreeShipping: true,
offerInternational: true,
internationalDays: "10-21",
internationalCost: "24.99",
restrictedCountries: "Russia, North Korea, Iran",
returnWindow: "30",
returnCondition: "unused, unworn, with tags",
refundMethod: "original payment method",
returnShippingPaid: "customer",
offerExchanges: true,
damagedPolicy: true,
lostPackagePolicy: true,
holidayBlackout: "",
};
function Field({ label, hint, children }) {
return (
<div style={{ marginBottom: "20px" }}>
<label style={{ display: "block", fontFamily: "'DM Sans', sans-serif", fontWeight: 500, fontSize: "13px", letterSpacing: "0.08em", textTransform: "uppercase", color: "#8B7355", marginBottom: "6px" }}>
{label}
</label>
{hint && <p style={{ fontSize: "12px", color: "#B0A090", marginBottom: "8px", fontFamily: "'DM Sans', sans-serif" }}>{hint}</p>}
{children}
</div>
);
}
const inputStyle = {
width: "100%",
padding: "10px 14px",
background: "#FBF9F6",
border: "1.5px solid #E8DFD0",
borderRadius: "8px",
fontFamily: "'DM Sans', sans-serif",
fontSize: "15px",
color: "#2C1810",
outline: "none",
boxSizing: "border-box",
transition: "border-color 0.2s",
};
function Input({ value, onChange, placeholder, type = "text" }) {
const [focused, setFocused] = useState(false);
return (
<input
type={type}
value={value}
onChange={onChange}
placeholder={placeholder}
style={{ ...inputStyle, borderColor: focused ? "#C4956A" : "#E8DFD0" }}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
/>
);
}
function Toggle({ checked, onChange, label }) {
return (
<label style={{ display: "flex", alignItems: "center", gap: "10px", cursor: "pointer" }}>
<div
onClick={() => onChange(!checked)}
style={{
width: "44px", height: "24px", borderRadius: "12px",
background: checked ? "#C4956A" : "#D8CFC4",
position: "relative", transition: "background 0.2s", cursor: "pointer", flexShrink: 0
}}
>
<div style={{
position: "absolute", top: "3px",
left: checked ? "23px" : "3px",
width: "18px", height: "18px",
borderRadius: "50%", background: "#fff",
transition: "left 0.2s", boxShadow: "0 1px 3px rgba(0,0,0,0.2)"
}} />
</div>
<span style={{ fontFamily: "'DM Sans', sans-serif", fontSize: "14px", color: "#5C4033" }}>{label}</span>
</label>
);
}
function generatePolicy(d) {
const today = new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
return `SHIPPING & RETURNS POLICY
${d.businessName || "Our Store"}${d.website ? ` | ${d.website}` : ""}
Last Updated: ${today}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PROCESSING TIME
All orders are processed within ${d.processingDays} business days (excluding weekends and holidays). Orders placed after ${d.cutoffTime} will begin processing the following business day.
You will receive a confirmation email with tracking information once your order has shipped.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DOMESTIC SHIPPING${d.offerFreeShipping ? ` (United States)` : ""}
Standard Shipping (${d.standardDays} business days): $${d.standardCost}
Expedited Shipping (${d.expeditedDays} business days): $${d.expeditedCost}
Overnight Shipping (1 business day): $${d.overnightCost}
${d.offerFreeShipping ? `\nFREE Standard Shipping on all orders over $${d.freeShippingThreshold}!\n` : ""}
Delivery times are estimates and begin from the date of shipment, not the date of purchase. ${d.businessName || "We"} is not responsible for delays caused by weather, carrier issues, or other circumstances beyond our control.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
${d.offerInternational ? `
INTERNATIONAL SHIPPING
We ship to most countries worldwide. International orders typically arrive within ${d.internationalDays} business days from the shipment date.
International Shipping: Starting at $${d.internationalCost}
Please note:
- Customers are responsible for all customs duties, taxes, and import fees imposed by their country.
- ${d.businessName || "We"} is not responsible for delays caused by customs processing.
- We do not ship to the following restricted regions: ${d.restrictedCountries}.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
` : ""}
RETURNS & REFUNDS
We accept returns within ${d.returnWindow} days of the delivery date.
Eligibility: Items must be ${d.returnCondition} to qualify for a return.
Return Shipping: ${d.returnShippingPaid === "customer" ? "Customers are responsible for return shipping costs. We recommend using a trackable shipping service." : "We provide a prepaid return shipping label for all eligible returns."}
Refunds: Once your return is received and inspected, we will process your refund to the ${d.refundMethod} within 5-10 business days.
${d.offerExchanges ? "\nExchanges: We are happy to exchange items for a different size or color, subject to availability. Please contact us to initiate an exchange.\n" : ""}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
${d.damagedPolicy || d.lostPackagePolicy ? `
DAMAGED & LOST ORDERS
${d.damagedPolicy ? `\nDamaged Items: If your order arrives damaged, please contact us within 48 hours of delivery at ${d.email || "our support email"} with photos of the damage and your order number. We will arrange a replacement or full refund at no cost to you.` : ""}
${d.lostPackagePolicy ? `\nLost Packages: If your tracking shows delivered but you have not received your package, please wait 3 business days as packages are sometimes delivered to neighbors or left in an unexpected location. If still not found, contact us and we will work with the carrier to locate your package or provide a replacement.` : ""}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
` : ""}
${d.holidayBlackout ? `
HOLIDAY SHIPPING NOTICE
Please note the following blackout dates when orders may experience processing delays: ${d.holidayBlackout}.
We recommend placing orders at least 2 weeks before major holidays to ensure timely delivery.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
` : ""}
CONTACT US
Questions about your order? We're here to help.
${d.email ? `Email: ${d.email}` : ""}${d.website ? `\nWebsite: ${d.website}` : ""}
We aim to respond to all inquiries within 1-2 business days.`;
}
export default function App() {
const [step, setStep] = useState(0);
const [data, setData] = useState(defaultData);
const [generated, setGenerated] = useState(false);
const [copied, setCopied] = useState(false);
const set = (key) => (e) => setData((d) => ({ ...d, [key]: e.target ? e.target.value : e }));
const policy = generatePolicy(data);
const copy = () => {
navigator.clipboard.writeText(policy);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const renderStep = () => {
switch (steps[step].id) {
case "business":
return (
<>
<Field label="Business Name"><Input value={data.businessName} onChange={set("businessName")} placeholder="Acme Store Co." /></Field>
<Field label="Website URL"><Input value={data.website} onChange={set("website")} placeholder="https://acmestore.com" /></Field>
<Field label="Support Email"><Input value={data.email} onChange={set("email")} placeholder="support@acmestore.com" /></Field>
</>
);
case "domestic":
return (
<>
<Field label="Processing Time" hint="How many business days to process orders?"><Input value={data.processingDays} onChange={set("processingDays")} placeholder="1-3" /></Field>
<Field label="Order Cutoff Time"><Input value={data.cutoffTime} onChange={set("cutoffTime")} placeholder="2:00 PM EST" /></Field>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "16px" }}>
<Field label="Standard Delivery"><Input value={data.standardDays} onChange={set("standardDays")} placeholder="5-7 days" /></Field>
<Field label="Standard Cost ($)"><Input value={data.standardCost} onChange={set("standardCost")} placeholder="5.99" /></Field>
<Field label="Expedited Delivery"><Input value={data.expeditedDays} onChange={set("expeditedDays")} placeholder="2-3 days" /></Field>
<Field label="Expedited Cost ($)"><Input value={data.expeditedCost} onChange={set("expeditedCost")} placeholder="14.99" /></Field>
</div>
<Field label="Overnight Cost ($)"><Input value={data.overnightCost} onChange={set("overnightCost")} placeholder="29.99" /></Field>
<Toggle checked={data.offerFreeShipping} onChange={set("offerFreeShipping")} label="Offer free shipping threshold" />
{data.offerFreeShipping && (
<div style={{ marginTop: "12px" }}>
<Field label="Free Shipping Over ($)"><Input value={data.freeShippingThreshold} onChange={set("freeShippingThreshold")} placeholder="75" /></Field>
</div>
)}
</>
);
case "international":
return (
<>
<Toggle checked={data.offerInternational} onChange={set("offerInternational")} label="Ship internationally" />
{data.offerInternational && (
<>
<div style={{ height: "16px" }} />
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "16px" }}>
<Field label="Delivery Time"><Input value={data.internationalDays} onChange={set("internationalDays")} placeholder="10-21 days" /></Field>
<Field label="Starting Cost ($)"><Input value={data.internationalCost} onChange={set("internationalCost")} placeholder="24.99" /></Field>
</div>
<Field label="Restricted Countries/Regions" hint="Comma-separated list of places you don't ship to">
<Input value={data.restrictedCountries} onChange={set("restrictedCountries")} placeholder="Russia, North Korea..." />
</Field>
</>
)}
</>
);
case "returns":
return (
<>
<Field label="Return Window (days)"><Input value={data.returnWindow} onChange={set("returnWindow")} placeholder="30" /></Field>
<Field label="Return Condition Required"><Input value={data.returnCondition} onChange={set("returnCondition")} placeholder="unused, unworn, with tags" /></Field>
<Field label="Refund Issued To"><Input value={data.refundMethod} onChange={set("refundMethod")} placeholder="original payment method" /></Field>
<Field label="Who Pays Return Shipping?">
<div style={{ display: "flex", gap: "12px" }}>
{["customer", "store"].map((opt) => (
<label key={opt} style={{ display: "flex", alignItems: "center", gap: "8px", cursor: "pointer", fontFamily: "'DM Sans', sans-serif", fontSize: "14px", color: "#5C4033" }}>
<input type="radio" checked={data.returnShippingPaid === opt} onChange={() => setData(d => ({ ...d, returnShippingPaid: opt }))} style={{ accentColor: "#C4956A" }} />
{opt === "customer" ? "Customer pays" : "We provide label"}
</label>
))}
</div>
</Field>
<Toggle checked={data.offerExchanges} onChange={set("offerExchanges")} label="Offer exchanges" />
</>
);
case "special":
return (
<>
<Toggle checked={data.damagedPolicy} onChange={set("damagedPolicy")} label="Include damaged item policy" />
<div style={{ height: "12px" }} />
<Toggle checked={data.lostPackagePolicy} onChange={set("lostPackagePolicy")} label="Include lost package policy" />
<div style={{ height: "16px" }} />
<Field label="Holiday Blackout Dates" hint="Optional — leave blank to skip">
<Input value={data.holidayBlackout} onChange={set("holidayBlackout")} placeholder="Dec 24 – Jan 2, Thanksgiving week" />
</Field>
</>
);
}
};
return (
<>
<style>{FONTS}</style>
<div style={{ minHeight: "100vh", background: "#F5F0E8", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: "40px 16px", fontFamily: "'DM Sans', sans-serif" }}>
<div style={{ width: "100%", maxWidth: "760px" }}>
{/* Header */}
<div style={{ textAlign: "center", marginBottom: "40px" }}>
<div style={{ fontSize: "11px", letterSpacing: "0.2em", textTransform: "uppercase", color: "#C4956A", marginBottom: "10px", fontWeight: 500 }}>Policy Generator</div>
<h1 style={{ fontFamily: "'Playfair Display', serif", fontSize: "clamp(32px, 5vw, 52px)", fontWeight: 900, color: "#2C1810", margin: 0, lineHeight: 1.1 }}>
Shipping Policy
</h1>
<p style={{ color: "#8B7355", marginTop: "12px", fontSize: "16px", fontWeight: 300 }}>
Craft a professional policy in minutes
</p>
</div>
{!generated ? (
<div style={{ background: "#FFFCF7", borderRadius: "20px", border: "1.5px solid #E8DFD0", overflow: "hidden", boxShadow: "0 4px 40px rgba(44,24,16,0.07)" }}>
{/* Step Nav */}
<div style={{ display: "flex", borderBottom: "1.5px solid #E8DFD0", overflowX: "auto" }}>
{steps.map((s, i) => (
<button
key={s.id}
onClick={() => setStep(i)}
style={{
flex: 1, minWidth: "80px", padding: "16px 8px", background: "none", border: "none",
borderBottom: step === i ? "2.5px solid #C4956A" : "2.5px solid transparent",
cursor: "pointer", transition: "all 0.2s",
color: step === i ? "#C4956A" : "#A09080",
}}
>
<div style={{ fontSize: "18px", marginBottom: "4px" }}>{s.icon}</div>
<div style={{ fontSize: "11px", fontWeight: 500, letterSpacing: "0.05em", textTransform: "uppercase" }}>{s.label}</div>
</button>
))}
</div>
{/* Form */}
<div style={{ padding: "32px" }}>
<h2 style={{ fontFamily: "'Playfair Display', serif", fontSize: "22px", color: "#2C1810", margin: "0 0 24px 0" }}>
{steps[step].icon} {steps[step].label}
</h2>
{renderStep()}
</div>
{/* Nav Buttons */}
<div style={{ padding: "0 32px 32px", display: "flex", justifyContent: "space-between", gap: "12px" }}>
<button
onClick={() => setStep(s => Math.max(0, s - 1))}
disabled={step === 0}
style={{ padding: "12px 24px", background: "none", border: "1.5px solid #D8CFC4", borderRadius: "10px", color: "#8B7355", cursor: step === 0 ? "not-allowed" : "pointer", opacity: step === 0 ? 0.4 : 1, fontFamily: "'DM Sans', sans-serif", fontWeight: 500, fontSize: "14px" }}
>
← Back
</button>
{step < steps.length - 1 ? (
<button
onClick={() => setStep(s => s + 1)}
style={{ padding: "12px 32px", background: "#C4956A", border: "none", borderRadius: "10px", color: "#fff", cursor: "pointer", fontFamily: "'DM Sans', sans-serif", fontWeight: 500, fontSize: "14px", boxShadow: "0 2px 12px rgba(196,149,106,0.35)" }}
>
Next →
</button>
) : (
<button
onClick={() => setGenerated(true)}
style={{ padding: "12px 32px", background: "#2C1810", border: "none", borderRadius: "10px", color: "#F5F0E8", cursor: "pointer", fontFamily: "'DM Sans', sans-serif", fontWeight: 500, fontSize: "14px", boxShadow: "0 2px 16px rgba(44,24,16,0.25)" }}
>
✨ Generate Policy
</button>
)}
</div>
</div>
) : (
<div>
{/* Actions */}
<div style={{ display: "flex", gap: "12px", marginBottom: "20px", flexWrap: "wrap" }}>
<button onClick={() => setGenerated(false)} style={{ padding: "10px 20px", background: "none", border: "1.5px solid #D8CFC4", borderRadius: "10px", color: "#8B7355", cursor: "pointer", fontFamily: "'DM Sans', sans-serif", fontWeight: 500, fontSize: "14px" }}>
← Edit
</button>
<button onClick={copy} style={{ padding: "10px 24px", background: copied ? "#7B9E6B" : "#C4956A", border: "none", borderRadius: "10px", color: "#fff", cursor: "pointer", fontFamily: "'DM Sans', sans-serif", fontWeight: 500, fontSize: "14px", transition: "background 0.2s", marginLeft: "auto" }}>
{copied ? "✓ Copied!" : "📋 Copy Policy"}
</button>
</div>
{/* Policy Output */}
<div style={{ background: "#FFFCF7", borderRadius: "20px", border: "1.5px solid #E8DFD0", padding: "40px", boxShadow: "0 4px 40px rgba(44,24,16,0.07)" }}>
<pre style={{ fontFamily: "'DM Sans', sans-serif", fontSize: "14px", lineHeight: 1.8, color: "#3C2818", whiteSpace: "pre-wrap", margin: 0 }}>
{policy}
</pre>
</div>
</div>
)}
</div>
</div>
</>
);
}