@womp/mcp-ui 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/cart/index.html +284 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +23 -5
- package/dist/order/index.html +187 -0
- package/dist/print/index.html +21 -4
- package/package.json +5 -3
- package/src/cart/index.html +210 -0
- package/src/cart/main.ts +157 -0
- package/src/order/index.html +113 -0
- package/src/order/main.ts +110 -0
- package/src/print/index.html +20 -3
- package/src/print/main.ts +85 -28
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Womp orders</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root {
|
|
8
|
+
color-scheme: light dark;
|
|
9
|
+
}
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
font-family: var(--font-sans, system-ui, -apple-system, "Segoe UI", sans-serif);
|
|
13
|
+
color: var(--color-text-primary, #e8e8e8);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.card {
|
|
17
|
+
max-width: 400px;
|
|
18
|
+
border-radius: var(--border-radius-lg, 14px);
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
background: var(--color-background-secondary, #1b1b1d);
|
|
21
|
+
border: 1px solid var(--color-border-primary, rgba(255, 255, 255, 0.08));
|
|
22
|
+
padding: 14px;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.orders { display: grid; gap: 12px; }
|
|
27
|
+
|
|
28
|
+
.order { display: grid; gap: 6px; }
|
|
29
|
+
.order + .order {
|
|
30
|
+
border-top: 1px solid var(--color-border-primary, rgba(255, 255, 255, 0.08));
|
|
31
|
+
padding-top: 12px;
|
|
32
|
+
}
|
|
33
|
+
.order-head {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
gap: 8px;
|
|
38
|
+
}
|
|
39
|
+
.order-id {
|
|
40
|
+
font-size: var(--font-text-sm-size, 13px);
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
}
|
|
43
|
+
/* Status chips mirror the orders page's wording; color encodes the band:
|
|
44
|
+
waiting (amber), moving (blue), done (green), stopped (red/gray). */
|
|
45
|
+
.chip {
|
|
46
|
+
font-size: 10px;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
letter-spacing: 0.04em;
|
|
49
|
+
padding: 2px 8px;
|
|
50
|
+
border-radius: var(--border-radius-full, 999px);
|
|
51
|
+
background: var(--color-background-tertiary, #2a2a2e);
|
|
52
|
+
color: var(--color-text-secondary, rgba(232, 232, 232, 0.7));
|
|
53
|
+
white-space: nowrap;
|
|
54
|
+
}
|
|
55
|
+
.chip.wait { background: rgba(217, 174, 85, 0.16); color: #d9ae55; }
|
|
56
|
+
.chip.move { background: rgba(122, 162, 255, 0.16); color: #7aa2ff; }
|
|
57
|
+
.chip.done { background: rgba(127, 199, 150, 0.16); color: #7fc796; }
|
|
58
|
+
.chip.stop { background: rgba(224, 141, 141, 0.16); color: #e08d8d; }
|
|
59
|
+
|
|
60
|
+
.lines {
|
|
61
|
+
font-size: var(--font-text-xs-size, 11px);
|
|
62
|
+
color: var(--color-text-secondary, rgba(232, 232, 232, 0.6));
|
|
63
|
+
display: grid;
|
|
64
|
+
gap: 2px;
|
|
65
|
+
}
|
|
66
|
+
.total-row {
|
|
67
|
+
display: flex;
|
|
68
|
+
justify-content: space-between;
|
|
69
|
+
font-size: var(--font-text-xs-size, 12px);
|
|
70
|
+
}
|
|
71
|
+
.total-row b { font-variant-numeric: tabular-nums; }
|
|
72
|
+
|
|
73
|
+
.action {
|
|
74
|
+
margin-top: 4px;
|
|
75
|
+
border-left: 3px solid #d9ae55;
|
|
76
|
+
background: rgba(217, 174, 85, 0.1);
|
|
77
|
+
border-radius: 6px;
|
|
78
|
+
padding: 8px 10px;
|
|
79
|
+
font-size: var(--font-text-xs-size, 11px);
|
|
80
|
+
color: var(--color-text-primary, #e8e8e8);
|
|
81
|
+
}
|
|
82
|
+
.action[hidden] { display: none; }
|
|
83
|
+
|
|
84
|
+
.tracking {
|
|
85
|
+
font-size: var(--font-text-xs-size, 11px);
|
|
86
|
+
color: var(--color-text-secondary, rgba(232, 232, 232, 0.7));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.empty {
|
|
90
|
+
padding: 14px 0;
|
|
91
|
+
font-size: var(--font-text-sm-size, 13px);
|
|
92
|
+
color: var(--color-text-secondary, rgba(232, 232, 232, 0.6));
|
|
93
|
+
text-align: center;
|
|
94
|
+
}
|
|
95
|
+
.empty[hidden] { display: none; }
|
|
96
|
+
|
|
97
|
+
.status {
|
|
98
|
+
margin-top: 8px;
|
|
99
|
+
font-size: var(--font-text-xs-size, 11px);
|
|
100
|
+
color: var(--color-text-secondary, rgba(232, 232, 232, 0.6));
|
|
101
|
+
}
|
|
102
|
+
.status:empty { display: none; }
|
|
103
|
+
</style>
|
|
104
|
+
</head>
|
|
105
|
+
<body>
|
|
106
|
+
<div class="card" id="card">
|
|
107
|
+
<div class="orders" id="orders"></div>
|
|
108
|
+
<div class="empty" id="empty" hidden>No print orders yet.</div>
|
|
109
|
+
<div class="status" id="status">Connecting…</div>
|
|
110
|
+
</div>
|
|
111
|
+
<script type="module" src="./main.ts"></script>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { $, createCard, type GenerationResult } from "../shared/bridge";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The order-status card — renders get_print_order (one order) and
|
|
5
|
+
* list_print_orders ({orders}) with the orders page's own status wording,
|
|
6
|
+
* colored by band: waiting / moving / done / stopped. A pending diff charge
|
|
7
|
+
* renders as the amber action strip so a blocked order explains itself.
|
|
8
|
+
* Read-only: no tool calls, no links out beyond what the model narrates.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface OrderSummary {
|
|
12
|
+
orderId: number;
|
|
13
|
+
status: string;
|
|
14
|
+
totalCost: number;
|
|
15
|
+
discount: number;
|
|
16
|
+
items: { title: string; materialName: string; quantity: number }[];
|
|
17
|
+
tracking: { carrier: string; trackingNumber: string }[];
|
|
18
|
+
actionRequired?: { type: string; amount: number };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface OrderPayload {
|
|
22
|
+
orders?: OrderSummary[];
|
|
23
|
+
orderId?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const usd = (n: number) => `$${n.toFixed(2)}`;
|
|
27
|
+
|
|
28
|
+
/** The orders page's label set, mapped to a color band. */
|
|
29
|
+
function chipClass(status: string): string {
|
|
30
|
+
const s = status.toLowerCase();
|
|
31
|
+
if (s.includes("pending") || s.includes("review") || s.includes("action")) return "wait";
|
|
32
|
+
if (s.includes("printing") || s.includes("processing") || s.includes("way") || s.includes("returning"))
|
|
33
|
+
return "move";
|
|
34
|
+
if (s.includes("delivered")) return "done";
|
|
35
|
+
if (s.includes("canceled")) return "stop";
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function orderBlock(order: OrderSummary): HTMLElement {
|
|
40
|
+
const block = document.createElement("div");
|
|
41
|
+
block.className = "order";
|
|
42
|
+
|
|
43
|
+
const head = document.createElement("div");
|
|
44
|
+
head.className = "order-head";
|
|
45
|
+
const id = document.createElement("div");
|
|
46
|
+
id.className = "order-id";
|
|
47
|
+
id.textContent = `Order #${order.orderId}`;
|
|
48
|
+
const chip = document.createElement("span");
|
|
49
|
+
chip.className = `chip ${chipClass(order.status)}`.trim();
|
|
50
|
+
chip.textContent = order.status;
|
|
51
|
+
head.append(id, chip);
|
|
52
|
+
block.append(head);
|
|
53
|
+
|
|
54
|
+
const lines = document.createElement("div");
|
|
55
|
+
lines.className = "lines";
|
|
56
|
+
(order.items || []).forEach((item) => {
|
|
57
|
+
const line = document.createElement("div");
|
|
58
|
+
line.textContent = `${item.quantity}x ${item.title} (${item.materialName})`;
|
|
59
|
+
lines.append(line);
|
|
60
|
+
});
|
|
61
|
+
block.append(lines);
|
|
62
|
+
|
|
63
|
+
const total = document.createElement("div");
|
|
64
|
+
total.className = "total-row";
|
|
65
|
+
const label = document.createElement("span");
|
|
66
|
+
label.textContent = order.discount > 0 ? `Total (after ${usd(order.discount)} discount)` : "Total";
|
|
67
|
+
const value = document.createElement("b");
|
|
68
|
+
value.textContent = usd(order.totalCost);
|
|
69
|
+
total.append(label, value);
|
|
70
|
+
block.append(total);
|
|
71
|
+
|
|
72
|
+
(order.tracking || []).forEach((t) => {
|
|
73
|
+
const track = document.createElement("div");
|
|
74
|
+
track.className = "tracking";
|
|
75
|
+
track.textContent = `Tracking: ${t.carrier ? `${t.carrier} ` : ""}${t.trackingNumber}`;
|
|
76
|
+
block.append(track);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (order.actionRequired) {
|
|
80
|
+
const action = document.createElement("div");
|
|
81
|
+
action.className = "action";
|
|
82
|
+
action.textContent =
|
|
83
|
+
`Action required: the measured piece costs ${usd(order.actionRequired.amount)} more than quoted. ` +
|
|
84
|
+
`Approve the adjustment on womp.com under Profile -> Orders to unblock production.`;
|
|
85
|
+
block.append(action);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return block;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function render(data: OrderPayload) {
|
|
92
|
+
// list_print_orders sends {orders}; get_print_order sends one order flat.
|
|
93
|
+
const orders: OrderSummary[] = data.orders ?? (data.orderId ? [data as unknown as OrderSummary] : []);
|
|
94
|
+
const list = $("orders");
|
|
95
|
+
list.textContent = "";
|
|
96
|
+
orders.forEach((order) => list.append(orderBlock(order)));
|
|
97
|
+
$("empty").hidden = orders.length > 0;
|
|
98
|
+
$("status").textContent = "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
createCard("Womp orders", {
|
|
102
|
+
onResult: (raw: GenerationResult) => {
|
|
103
|
+
const data = raw as OrderPayload & GenerationResult;
|
|
104
|
+
if (data.status === "running") {
|
|
105
|
+
$("status").textContent = "Loading…";
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
render(data);
|
|
109
|
+
},
|
|
110
|
+
});
|
package/src/print/index.html
CHANGED
|
@@ -150,7 +150,21 @@
|
|
|
150
150
|
}
|
|
151
151
|
.status:empty { display: none; }
|
|
152
152
|
.status.error { color: var(--color-text-danger, #ff8888); }
|
|
153
|
-
|
|
153
|
+
.material-row {
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 6px;
|
|
157
|
+
}
|
|
158
|
+
/* Small, and it disappears entirely when the catalog has no swatch for the
|
|
159
|
+
selection — an empty box next to a picker reads as a broken image. */
|
|
160
|
+
.swatch {
|
|
161
|
+
width: 28px;
|
|
162
|
+
height: 20px;
|
|
163
|
+
object-fit: cover;
|
|
164
|
+
border-radius: 4px;
|
|
165
|
+
flex: none;
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
154
168
|
</head>
|
|
155
169
|
<body>
|
|
156
170
|
<div class="card" id="card">
|
|
@@ -162,7 +176,10 @@
|
|
|
162
176
|
<div class="controls" id="controls" hidden>
|
|
163
177
|
<label class="field">
|
|
164
178
|
material
|
|
165
|
-
<
|
|
179
|
+
<span class="material-row">
|
|
180
|
+
<select id="material"></select>
|
|
181
|
+
<img id="swatch" class="swatch" alt="" hidden />
|
|
182
|
+
</span>
|
|
166
183
|
</label>
|
|
167
184
|
<label class="field">
|
|
168
185
|
size (cm)
|
|
@@ -181,7 +198,7 @@
|
|
|
181
198
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
182
199
|
<path d="M7 17 17 7M9 7h8v8" />
|
|
183
200
|
</svg>
|
|
184
|
-
<span id="order-label">
|
|
201
|
+
<span id="order-label">Add to cart</span>
|
|
185
202
|
</button>
|
|
186
203
|
<div class="note" id="note" hidden>
|
|
187
204
|
Shipping is added at checkout; the final price is confirmed before printing.
|
package/src/print/main.ts
CHANGED
|
@@ -10,10 +10,12 @@ import { $, callTool, createCard, openLink, type GenerationResult } from "../sha
|
|
|
10
10
|
* the tool with them: the host announces the call's arguments (`ontoolinput`)
|
|
11
11
|
* before any result exists, and the controls initialize from that.
|
|
12
12
|
*
|
|
13
|
-
* Deliberately NO payment surface: "
|
|
14
|
-
* `
|
|
15
|
-
* `ui/open-link`
|
|
16
|
-
*
|
|
13
|
+
* Deliberately NO payment surface: "Add to cart" calls the app-callable
|
|
14
|
+
* `add_to_cart` (creates a free project + a real cart item), and the button
|
|
15
|
+
* then becomes "Checkout on womp.com" — a `ui/open-link` to the cart checkout
|
|
16
|
+
* at /pay/<ids>, where address, shipping, promo codes and payment all run in
|
|
17
|
+
* the one existing cart UI. Turning any knob after an add resets the button
|
|
18
|
+
* to "Add to cart" so the checkout link always matches the shown settings.
|
|
17
19
|
*
|
|
18
20
|
* Static mode: a result that already carries `printUrl` (the model called
|
|
19
21
|
* prepare_print directly) renders as a receipt — no controls, the button just
|
|
@@ -39,16 +41,34 @@ interface PrintQuoteResult {
|
|
|
39
41
|
callerIsPro?: boolean;
|
|
40
42
|
/** Which mesh was quoted — what re-quotes and prepare_print are keyed on. */
|
|
41
43
|
source?: { generationId?: number; glbUrl?: string };
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Present on estimate results only — populates the material picker.
|
|
46
|
+
*
|
|
47
|
+
* `label` and `swatchUrl` come from the backend material catalog
|
|
48
|
+
* (swan-backend docs/PRINT_MATERIALS.md). Before it existed this was a list
|
|
49
|
+
* of pricing ids, so the picker read "resin-clear-glossy" where the print
|
|
50
|
+
* flow says "Clear". Both stay optional: an id with no catalog entry yet
|
|
51
|
+
* still has to appear.
|
|
52
|
+
*/
|
|
53
|
+
materials?: PrintMaterialOption[];
|
|
44
54
|
projectId?: number;
|
|
45
55
|
/** Present on prepare_print results only. */
|
|
46
56
|
printUrl?: string;
|
|
47
57
|
}
|
|
48
58
|
|
|
59
|
+
interface PrintMaterialOption {
|
|
60
|
+
id: string;
|
|
61
|
+
label?: string;
|
|
62
|
+
group?: "standard" | "enterprise";
|
|
63
|
+
swatchUrl?: string;
|
|
64
|
+
available?: boolean;
|
|
65
|
+
colorRequired?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
const usd = (n: number) => `$${n.toFixed(2)}`;
|
|
50
69
|
|
|
51
70
|
const materialSelect = $("material") as HTMLSelectElement;
|
|
71
|
+
const swatchImg = $("swatch") as HTMLImageElement;
|
|
52
72
|
const longestInput = $("longest") as HTMLInputElement;
|
|
53
73
|
const quantityInput = $("quantity") as HTMLInputElement;
|
|
54
74
|
const orderBtn = $("order") as HTMLButtonElement;
|
|
@@ -56,6 +76,8 @@ const orderBtn = $("order") as HTMLButtonElement;
|
|
|
56
76
|
/** The mesh being quoted, and whether the card is a receipt or a configurator. */
|
|
57
77
|
let source: { generationId?: number; glbUrl?: string } | undefined;
|
|
58
78
|
let staticPrintUrl: string | undefined;
|
|
79
|
+
/** Set once the shown settings are in the cart; cleared by any knob turn. */
|
|
80
|
+
let cartCheckoutUrl = "";
|
|
59
81
|
/** Monotonic guard: a slow older quote must never overwrite a newer one. */
|
|
60
82
|
let quoteSeq = 0;
|
|
61
83
|
let debounceTimer: number | undefined;
|
|
@@ -121,20 +143,45 @@ function syncControls(data: PrintQuoteResult) {
|
|
|
121
143
|
materialSelect.append(opt);
|
|
122
144
|
}
|
|
123
145
|
materialSelect.value = data.material;
|
|
146
|
+
showSwatch();
|
|
124
147
|
}
|
|
125
148
|
if (typeof data.longestSideCm === "number") longestInput.value = String(Math.round(data.longestSideCm * 10) / 10);
|
|
126
149
|
if (typeof data.quantity === "number") quantityInput.value = String(data.quantity);
|
|
127
150
|
}
|
|
128
151
|
|
|
129
|
-
|
|
152
|
+
/** Swatches by material id, so the preview can follow the selection. */
|
|
153
|
+
let swatches: Record<string, string> = {};
|
|
154
|
+
|
|
155
|
+
function populateMaterials(materials: PrintMaterialOption[], selected?: string) {
|
|
130
156
|
materialSelect.textContent = "";
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
157
|
+
swatches = {};
|
|
158
|
+
// Grouped the way the print flow groups them. An Enterprise material this
|
|
159
|
+
// account cannot order is shown DISABLED rather than dropped: the reader can
|
|
160
|
+
// see it exists, which is the same thing the web panel does.
|
|
161
|
+
for (const group of ["standard", "enterprise"] as const) {
|
|
162
|
+
const rows = materials.filter((m) => (m.group || "standard") === group);
|
|
163
|
+
if (!rows.length) continue;
|
|
164
|
+
const holder =
|
|
165
|
+
group === "enterprise" ? Object.assign(document.createElement("optgroup"), { label: "Enterprise" }) : null;
|
|
166
|
+
for (const m of rows) {
|
|
167
|
+
const opt = document.createElement("option");
|
|
168
|
+
opt.value = m.id;
|
|
169
|
+
opt.textContent = m.label || m.id;
|
|
170
|
+
if (m.available === false) opt.disabled = true;
|
|
171
|
+
(holder || materialSelect).append(opt);
|
|
172
|
+
if (m.swatchUrl) swatches[m.id] = m.swatchUrl;
|
|
173
|
+
}
|
|
174
|
+
if (holder) materialSelect.append(holder);
|
|
136
175
|
}
|
|
137
176
|
if (selected) materialSelect.value = selected;
|
|
177
|
+
showSwatch();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The selected material's preview image, when the catalog has one. */
|
|
181
|
+
function showSwatch() {
|
|
182
|
+
const url = swatches[materialSelect.value];
|
|
183
|
+
swatchImg.src = url || "";
|
|
184
|
+
swatchImg.hidden = !url;
|
|
138
185
|
}
|
|
139
186
|
|
|
140
187
|
function currentSettings() {
|
|
@@ -176,10 +223,17 @@ function scheduleRequote() {
|
|
|
176
223
|
// Same idea as the web print panel's debounce: a slider being dragged must
|
|
177
224
|
// not fire an engine estimate per pixel.
|
|
178
225
|
if (debounceTimer !== undefined) clearTimeout(debounceTimer);
|
|
226
|
+
// The settings changed: whatever was added to the cart no longer matches
|
|
227
|
+
// what the card shows, so the button goes back to adding.
|
|
228
|
+
cartCheckoutUrl = "";
|
|
229
|
+
$("order-label").textContent = "Add to cart";
|
|
179
230
|
debounceTimer = window.setTimeout(() => void requote(), 600);
|
|
180
231
|
}
|
|
181
232
|
|
|
182
|
-
materialSelect.addEventListener("change",
|
|
233
|
+
materialSelect.addEventListener("change", () => {
|
|
234
|
+
showSwatch();
|
|
235
|
+
scheduleRequote();
|
|
236
|
+
});
|
|
183
237
|
longestInput.addEventListener("change", scheduleRequote);
|
|
184
238
|
quantityInput.addEventListener("change", scheduleRequote);
|
|
185
239
|
|
|
@@ -223,30 +277,33 @@ orderBtn.addEventListener("click", async () => {
|
|
|
223
277
|
void openLink(card.app, staticPrintUrl);
|
|
224
278
|
return;
|
|
225
279
|
}
|
|
280
|
+
if (cartCheckoutUrl) {
|
|
281
|
+
void openLink(card.app, cartCheckoutUrl);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
226
284
|
if (!source) return;
|
|
227
285
|
orderBtn.disabled = true;
|
|
228
|
-
$("order-label").textContent = "
|
|
286
|
+
$("order-label").textContent = "Adding to cart…";
|
|
229
287
|
setStatus("");
|
|
230
288
|
try {
|
|
231
|
-
const res = await callTool(card.app, "
|
|
232
|
-
const data = (res.structuredContent || {}) as
|
|
233
|
-
if (res.isError || !data.
|
|
234
|
-
const text = (res.content as any[])?.find((c) => c.type === "text")?.text || "Could not
|
|
289
|
+
const res = await callTool(card.app, "add_to_cart", currentSettings());
|
|
290
|
+
const data = (res.structuredContent || {}) as { payUrl?: string; items?: unknown[]; subtotal?: number };
|
|
291
|
+
if (res.isError || !data.payUrl) {
|
|
292
|
+
const text = (res.content as any[])?.find((c) => c.type === "text")?.text || "Could not add to the cart.";
|
|
235
293
|
setStatus(String(text), true);
|
|
294
|
+
$("order-label").textContent = "Add to cart";
|
|
236
295
|
return;
|
|
237
296
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
$("order-label").textContent = "Complete order on womp.com";
|
|
245
|
-
void openLink(card.app, data.printUrl);
|
|
297
|
+
cartCheckoutUrl = data.payUrl;
|
|
298
|
+
$("order-label").textContent = "Checkout on womp.com";
|
|
299
|
+
setStatus(
|
|
300
|
+
`Added — cart has ${data.items?.length ?? 1} item(s), subtotal $${(data.subtotal ?? 0).toFixed(2)}. ` +
|
|
301
|
+
`Shipping and promo codes apply at checkout.`,
|
|
302
|
+
);
|
|
246
303
|
} catch {
|
|
247
|
-
setStatus("Could not
|
|
304
|
+
setStatus("Could not add to the cart — try again.", true);
|
|
305
|
+
$("order-label").textContent = "Add to cart";
|
|
248
306
|
} finally {
|
|
249
307
|
orderBtn.disabled = false;
|
|
250
|
-
if (!staticPrintUrl) $("order-label").textContent = "Continue on womp.com";
|
|
251
308
|
}
|
|
252
309
|
});
|