/* ===========================================================================
VESTIRÉ STUDIO — bag, checkout & customer requests -> window
Cart · Checkout · Confirmation · My Requests (3 layouts) ·
Request tracking (3 status treatments) · Profile · Contact
=========================================================================== */
const { useState, useEffect, useRef, useMemo } = React;
/* ---- BAG / CART ---------------------------------------------------------- */
function CCart({ ctx }) {
const cart = ctx.cart;
const subtotal = cart.reduce((a, b) => a + b.unitPrice * b.qty, 0);
const deposit = cart.some((c) => c.lineType === "rental") ? 1500 : 0;
if (cart.length === 0) return (
Your bag is empty
Add a gown to start a rental or purchase request.
ctx.nav("browse")}>Browse the collection
);
return (
Your bag
{cart.length} item{cart.length !== 1 ? "s" : ""} · review before submitting your request
{cart.map((c, i) => (
{c.name}
{c.lineType}
Size {c.size}{c.lineType === "rental" && c.rentalStart ? " · " + fmtDate(c.rentalStart) + " · " + c.rentalDays + "d" : ""}
{c.qty}
{VS.peso(c.unitPrice * c.qty)}
))}
Summary
Subtotal{VS.peso(subtotal)}
{deposit > 0 &&
Refundable deposit{VS.peso(deposit)}
}
Fitting & steamingIncluded
Estimated total
{VS.peso(subtotal + deposit)}
No payment is taken now. Submit your request and we'll confirm availability & payment.
ctx.nav("checkout")}>Continue to request
ctx.nav("browse")}>Keep browsing
);
}
/* ---- CHECKOUT ------------------------------------------------------------ */
function CCheckout({ ctx }) {
const u = ctx.currentUser || VS.currentUser;
const cart = ctx.cart;
const [form, setForm] = useState({ name: u.fullname, email: u.email, phone: u.phone, address: u.address });
const [pay, setPay] = useState("GCash");
const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
const subtotal = cart.reduce((a, b) => a + b.unitPrice * b.qty, 0);
const deposit = cart.some((c) => c.lineType === "rental") ? 1500 : 0;
if (cart.length === 0) { ctx.nav("browse"); return null; }
async function submit() {
const req = await ctx.submitRequest(form, pay);
if (req) ctx.nav("confirm", { id: req.id });
}
return (
ctx.nav("cart")}>Bag
Submit your request
Payment method
{[["GCash", "phone"], ["Cash", "bag"], ["Credit", "box"]].map(([m, ic]) => (
))}
Request summary
{cart.map((c, i) => (
{c.name}
{c.lineType} · {c.size} · x{c.qty}
{VS.peso(c.unitPrice * c.qty)}
))}
Subtotal{VS.peso(subtotal)}
{deposit > 0 &&
Deposit{VS.peso(deposit)}
}
Total{VS.peso(subtotal + deposit)}
Submit request
You'll be able to track & cancel this from My Requests.
);
}
/* ---- CONFIRMATION -------------------------------------------------------- */
function CConfirm({ ctx }) {
const req = ctx.requests.find((r) => r.id === ctx.route.params.id);
if (!req) { ctx.nav("requests"); return null; }
return (
Request {req.code}
Your request is in.
We've received your {req.type} request and will confirm availability shortly. Track every step from My Requests.
{req.items.map((it, i) => (
{it.name}{it.lineType} · {it.size}
{VS.peso(it.subtotal)}
))}
ctx.nav("requests")}>Track my requests
ctx.nav("browse")}>Continue browsing
);
}
/* ---- STATUS RENDERER (variation-aware) ---------------------------------- */
function StatusBlock({ req, ui, compact }) {
if (ui === "pill") {
const order = VS.statusOrder;
const idx = req.status === "Cancelled" ? 1 : order.indexOf(req.status);
const pct = req.status === "Cancelled" ? 100 : ((idx + 1) / order.length) * 100;
return (
{req.status === "Cancelled" ? "—" : Math.round(pct) + "%"}
);
}
if (ui === "timeline") return ;
return ;
}
/* ---- MY REQUESTS --------------------------------------------------------- */
function CRequests({ ctx }) {
const layout = ctx.t.requestsLayout;
const ui = ctx.t.statusUI;
const cu = ctx.currentUser || VS.currentUser;
const mine = ctx.requests.filter((r) => r.userId === cu.id).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
const [filter, setFilter] = useState("All");
const [sel, setSel] = useState(mine[0] ? mine[0].id : null);
const FILTERS = { All: () => true, Active: (r) => r.status === "Pending" || r.status === "Processing", Completed: (r) => r.status === "Completed", Cancelled: (r) => r.status === "Cancelled" };
const list = mine.filter(FILTERS[filter]);
const header = (
Your account
My requests
ctx.nav("browse")}>New request
{Object.keys(FILTERS).map((f) => {
const n = mine.filter(FILTERS[f]).length;
return ;
})}
);
if (mine.length === 0) return (
{header}
No requests yet
ctx.nav("browse")}>Browse the collection
);
/* --- LAYOUT: SPLIT (master-detail) --- */
if (layout === "split") {
const current = list.find((r) => r.id === sel) || list[0];
return (
{header}
{list.map((r) => (
))}
{current &&
}
);
}
/* --- LAYOUT: TABLE --- */
if (layout === "table") {
return (
{header}
| Request | Items | Type | Submitted | Status | Total | |
{list.map((r) => (
ctx.nav("request", { id: r.id })}>
| {r.code} |
{r.items.slice(0, 3).map((it, i) =>  )} {r.items.length} piece{r.items.length !== 1 ? "s" : ""} |
{r.type} |
{fmtDate(r.createdAt)} |
|
{VS.peso(r.total)} |
|
))}
);
}
/* --- LAYOUT: CARDS (default) --- */
return (
{header}
{list.map((r) => (
{r.code}{r.type}
Submitted {fmtDate(r.createdAt)} · {r.payment}
{VS.peso(r.total)}{r.items.length} piece{r.items.length !== 1 ? "s" : ""}
{r.items.map((it, i) => (
{it.name}{it.lineType} · {it.size} · x{it.qty}
))}
{r.status === "Pending" && "Awaiting our confirmation"}
{r.status === "Processing" && (r.type === "rental" ? "In preparation · pickup " + fmtDate(r.rentalStart) : "Preparing your gown")}
{r.status === "Completed" && "Completed " + fmtDate(r.updatedAt)}
{r.status === "Cancelled" && "Cancelled " + fmtDate(r.updatedAt)}
{r.status === "Pending" && ctx.cancelRequest(r.id)}>Cancel}
ctx.nav("request", { id: r.id })}>Track
))}
);
}
/* ---- REQUEST DETAIL CARD (reused in split + full page) ------------------- */
function RequestDetailCard({ req, ctx, ui, embedded }) {
return (
{req.code}
{req.type}
Submitted {fmtDate(req.createdAt)} · paid via {req.payment}
{ui === "timeline" ? : }
Pieces in this request
{req.items.map((it, i) => (
{it.name}
{it.lineType} · size {it.size} · qty {it.qty}{it.lineType === "rental" && req.rentalStart ? " · " + fmtDate(req.rentalStart) + " (" + req.rentalDays + "d)" : ""}
{VS.peso(it.subtotal)}
))}
Delivery to
{req.customer.name}
{req.customer.address}
{req.customer.phone} · {req.customer.email}
Total
{VS.peso(req.total)}
ctx.nav("contact")}>Message the studio
{req.status === "Pending" && ctx.cancelRequest(req.id)}>Cancel request}
{!embedded && ctx.nav("requests")}>Back to requests}
);
}
function CRequestDetail({ ctx }) {
const req = ctx.requests.find((r) => r.id === ctx.route.params.id);
if (!req) { ctx.nav("requests"); return null; }
return (
ctx.nav("requests")}>My requests
);
}
/* ---- PROFILE ------------------------------------------------------------- */
function CProfile({ ctx }) {
const u = ctx.currentUser || VS.currentUser;
const mine = ctx.requests.filter((r) => r.userId === u.id);
const stats = [["Total requests", mine.length], ["Completed", mine.filter((r) => r.status === "Completed").length], ["Active", mine.filter((r) => r.status === "Pending" || r.status === "Processing").length]];
return (
);
}
/* ---- CONTACT ------------------------------------------------------------- */
function CContact({ ctx }) {
const me = ctx.currentUser;
const cu = me || { fullname: "", email: "" };
const [form, setForm] = useState({ name: cu.fullname, email: cu.email, subject: "", body: "" });
const [sent, setSent] = useState(false);
const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
function send() {
if (!form.body.trim()) return;
ctx.addMessage(form);
setSent(true);
}
return (
Get in touch
Visit the atelier
Questions about a fitting, a rental window, or a custom piece? Send a note and we'll reply within a day.
{[["pin", "Door 2, Madapo Hills, Davao City"], ["phone", "+63 912 345 6789"], ["mail", "hello@vestirestudio.com"], ["clock", "Mon-Sat · 10am - 7pm"]].map(([ic, t]) => (
{t}
))}
{!me ? (
Sign in to message us
Messaging the studio requires an account so we can reply to you and keep your conversation on record.
ctx.nav("login")}>Sign in
ctx.nav("register")}>Create account
) : (
)}
);
}
Object.assign(window, { CCart, CCheckout, CConfirm, CRequests, CRequestDetail, RequestDetailCard, StatusBlock, CProfile, CContact });