import { useState, useEffect, useCallback } from "react"; const API = "https://3marketplace.shop"; const COLORS = { bg: "#0a0a0f", surface: "#12121a", card: "#1a1a26", border: "#2a2a3d", accent: "#4f6ef7", gold: "#f0b429", green: "#22c55e", red: "#ef4444", cyan: "#06b6d4", text: "#e8e8f0", muted: "#6b6b8a", }; const CATEGORIES = ["all", "Electronics", "Fashion", "Gaming", "Sports", "Home", "Books", "Cars", "Other"]; const CONDITIONS = ["new", "like_new", "good", "used", "for_parts"]; const api = { post: async (path, body, token) => { const res = await fetch(`${API}${path}`, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}) }, body: JSON.stringify(body), }); return res.json(); }, get: async (path, token) => { const res = await fetch(`${API}${path}`, { headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}) }, }); return res.json(); }, delete: async (path, token) => { const res = await fetch(`${API}${path}`, { method: "DELETE", headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}) }, }); return res.json(); }, }; const formatEur = (n) => new Intl.NumberFormat("hr-HR", { style: "currency", currency: "EUR" }).format(n || 0); const s = { root: { minHeight: "100vh", background: COLORS.bg, color: COLORS.text, fontFamily: "'DM Sans', sans-serif" }, glow: { position: "fixed", top: -200, left: "50%", transform: "translateX(-50%)", width: 800, height: 400, background: "radial-gradient(ellipse, #4f6ef718 0%, transparent 70%)", pointerEvents: "none", zIndex: 0 }, wrap: { maxWidth: 1100, margin: "0 auto", padding: "0 20px", position: "relative", zIndex: 1 }, card: { background: COLORS.card, border: `1px solid ${COLORS.border}`, borderRadius: 16, padding: 20 }, btn: (color = COLORS.accent, outline = false) => ({ background: outline ? "transparent" : color, color: outline ? color : "#fff", border: `1.5px solid ${color}`, borderRadius: 10, padding: "10px 20px", cursor: "pointer", fontSize: 13, fontWeight: 700, transition: "all 0.18s" }), input: { background: COLORS.surface, border: `1.5px solid ${COLORS.border}`, borderRadius: 10, padding: "11px 14px", color: COLORS.text, fontSize: 14, outline: "none", width: "100%", boxSizing: "border-box" }, }; function Notification({ msg, type }) { if (!msg) return null; const color = type === "error" ? COLORS.red : COLORS.green; return (
{msg}
); } // ─── AUTH ─── function AuthScreen({ onLogin, notify }) { const [mode, setMode] = useState("login"); const [form, setForm] = useState({ email: "", password: "", username: "", location: "Croatia" }); const [loading, setLoading] = useState(false); const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value })); const submit = async () => { if (!form.email || !form.password) return notify("Email and password required", "error"); setLoading(true); try { if (mode === "register") { const res = await api.post("/auth/register", form); if (res.error) return notify(res.error, "error"); notify("Account created! Please log in."); setMode("login"); } else { const res = await api.post("/auth/login", { email: form.email, password: form.password }); if (res.error) return notify(res.error, "error"); localStorage.setItem("3m_token", res.token); localStorage.setItem("3m_user", JSON.stringify(res.user)); onLogin(res.user, res.token); } } catch { notify("Connection error", "error"); } finally { setLoading(false); } }; return (
🛒

3Market

Tri Kolektiv — zero fee marketplace

{["login", "register"].map((m) => ( ))}
{mode === "register" && ( <> )} e.key === "Enter" && submit()} /> e.key === "Enter" && submit()} />
); } // ─── LISTING CARD ─── function ListingCard({ listing, onBuy, isOwner, onDelete }) { const cashbackPct = listing.xmr_accepted ? 5 : 2; const cashback = parseFloat((listing.price * cashbackPct / 100).toFixed(2)); return (
{isOwner && ( )}
{listing.images?.[0] ? : "📦"}
{listing.title}
{listing.seller_username} · {listing.location}
{listing.category} {listing.condition?.replace("_", " ")} {listing.xmr_accepted && ⊛ XMR} {listing.allow_barter && ↔ Barter}
{listing.description &&
{listing.description.slice(0, 100)}{listing.description.length > 100 ? "..." : ""}
}
{formatEur(listing.price)}
+{formatEur(cashback)} cashback ({cashbackPct}%)
{!isOwner && }
); } // ─── NEW LISTING FORM ─── function NewListingForm({ token, onCreated, notify, onClose }) { const [form, setForm] = useState({ title: "", description: "", price: "", category: "Electronics", condition: "used", location: "", allow_barter: false, xmr_accepted: false }); const [loading, setLoading] = useState(false); const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.type === "checkbox" ? e.target.checked : e.target.value })); const submit = async () => { if (!form.title || !form.price) return notify("Title and price required", "error"); setLoading(true); const res = await api.post("/listings", form, token); setLoading(false); if (res.error) return notify(res.error, "error"); notify("Listing published! 🎉"); onCreated(); onClose(); }; return (
New Listing