@blamejs/blamejs-shop 0.3.54 → 0.3.55
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/CHANGELOG.md +2 -0
- package/lib/admin.js +87 -77
- package/lib/asset-manifest.json +5 -5
- package/lib/storefront.js +154 -47
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.3.x
|
|
10
10
|
|
|
11
|
+
- v0.3.55 (2026-06-02) — **Accessibility: field-level form-error messages and horizontally scrollable admin tables.** Two accessibility improvements. When a storefront form is rejected, the specific field at fault is now marked for assistive technology with an inline error message tied to that input, in addition to the existing summary at the top of the form. And wide tables in the admin console now scroll horizontally inside their own container instead of overflowing the page on a narrow or zoomed viewport. **Fixed:** *Field-level error messages on storefront forms* — When the address, review, product-question, or support-ticket form is rejected, the offending field is now marked aria-invalid with an inline error message associated to it (aria-describedby), so a screen-reader user is taken to the exact field to fix rather than only hearing a top-of-form summary. The server remains the only validator; this only changes how a rejected field is presented. Forms that fail on a whole-form condition continue to show the summary message. · *Admin tables reflow on narrow screens* — Wide data tables in the admin console (orders, products, inventory, and the rest) are now wrapped in a horizontal-scroll container, so columns stay reachable on a smaller laptop window, a split screen, or at high zoom instead of overflowing the layout. Column header semantics are preserved.
|
|
12
|
+
|
|
11
13
|
- v0.3.54 (2026-06-02) — **In-store pickup, saved payment methods, and gift wrapping at checkout.** Three more built-but-unreachable capabilities are now wired end to end. Operators can offer in-store pickup with managed pickup locations and a fulfilment queue; signed-in customers can save a card and reuse it at checkout; and operators can offer gift wrapping with a per-order gift message. The gift-wrap fee is added as a real cart line so it is included in the charged total. **Added:** *In-store pickup (click and collect)* — Operators can define pickup locations and work a pickup queue in the admin console (filtered by location), and shoppers can choose 'Pick up in store' at checkout. The order page and a new account pickups view show the pickup status. A not-found pickup or location returns a clear not-found rather than a generic error. · *Saved payment methods* — When Stripe is configured, a signed-in customer can save a card via a secure setup flow (loaded through a route-scoped policy and a same-origin script, with no inline script), manage their saved cards under their account, set a default, and reuse a saved card at checkout. A customer can only ever see or change their own saved cards. · *Gift wrapping at checkout* — Operators can define gift-wrap options (with a fee) in the admin console, and shoppers can add a wrap plus a gift message and recipient at checkout. The wrap fee is added as a real cart line, so it is included in the order total and the amount charged — never applied after the charge.
|
|
12
14
|
|
|
13
15
|
- v0.3.53 (2026-06-02) — **Back-in-stock notifications, operator-tunable search ranking, and trust badges.** Three capabilities that were built into the framework but reachable from no route are now wired end to end. A shopper looking at an out-of-stock variant can ask to be emailed when it returns; operators can tune how search results are ordered and pin products for specific queries; and operators can author trust badges that render at checkout and on the order-confirmation page. **Added:** *Back-in-stock notifications* — When a variant is out of stock, the product page now shows a 'Notify me when it's back' form. The shopper confirms by email (double opt-in), and a scheduled sweep emails everyone waiting when stock returns, with a one-click unsubscribe. The notify form is served on the cached product page and is protected by same-site and fetch-metadata checks. · *Operator-tunable search ranking* — A new Search ranking screen in the admin console lets an operator define weighted ranking signals, activate a weight set, and pin specific products to the top for a given query. Ranking is applied to the search results; if a ranking configuration is missing or invalid, search falls back to its default order rather than erroring. · *Trust badges* — A new Trust badges screen lets an operator author badges (with scheduling windows) that render at checkout and on the order-confirmation page, with impression tracking. Badge content — including any SVG — is sanitized when saved.
|
package/lib/admin.js
CHANGED
|
@@ -181,6 +181,16 @@ function _htmlEscape(s) {
|
|
|
181
181
|
return b.template.escapeHtml(String(s));
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// Wrap a built data-table string in a horizontal-scroll container so a
|
|
185
|
+
// narrow viewport scrolls the table rather than the whole page (.table-wrap
|
|
186
|
+
// is defined in admin.css — WCAG 1.4.10 reflow). Layout-only tables
|
|
187
|
+
// (order-totals) are NOT wrapped — they never overflow. `tableHtml` is
|
|
188
|
+
// already-escaped markup assembled by the caller; this helper interpolates
|
|
189
|
+
// no untrusted data, so it is XSS-neutral.
|
|
190
|
+
function _tableWrap(tableHtml) {
|
|
191
|
+
return "<div class=\"table-wrap\">" + tableHtml + "</div>";
|
|
192
|
+
}
|
|
193
|
+
|
|
184
194
|
// Strict integer coercion for money / count form fields. Refuses
|
|
185
195
|
// "50abc" / "" / floats, unlike parseInt's loose prefix match (which
|
|
186
196
|
// silently turns "50abc" into 50). Accepts an already-numeric value
|
|
@@ -10508,7 +10518,7 @@ function renderDashboard(opts) {
|
|
|
10508
10518
|
}).join("");
|
|
10509
10519
|
otherCurrencies =
|
|
10510
10520
|
"<section><h2>Other currencies in window</h2><div class=\"panel\">" +
|
|
10511
|
-
"<table><thead><tr><th scope=\"col\">Currency</th><th scope=\"col\" class=\"num\">Orders</th><th scope=\"col\" class=\"num\">Revenue</th></tr></thead><tbody>" + rows + "</tbody></table>" +
|
|
10521
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">Currency</th><th scope=\"col\" class=\"num\">Orders</th><th scope=\"col\" class=\"num\">Revenue</th></tr></thead><tbody>" + rows + "</tbody></table>") +
|
|
10512
10522
|
"</div></section>";
|
|
10513
10523
|
}
|
|
10514
10524
|
|
|
@@ -10542,11 +10552,11 @@ function renderDashboard(opts) {
|
|
|
10542
10552
|
"<section><h2>Catalog + activity</h2><div class=\"two-col\">" +
|
|
10543
10553
|
" <div class=\"panel\">" +
|
|
10544
10554
|
" <h3 class=\"subhead\">Top SKUs by units sold</h3>" +
|
|
10545
|
-
" <table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Units</th><th scope=\"col\" class=\"num\">Revenue</th></tr></thead><tbody>" + topRows + "</tbody></table>" +
|
|
10555
|
+
" " + _tableWrap("<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Units</th><th scope=\"col\" class=\"num\">Revenue</th></tr></thead><tbody>" + topRows + "</tbody></table>") +
|
|
10546
10556
|
" </div>" +
|
|
10547
10557
|
" <div class=\"panel\">" +
|
|
10548
10558
|
" <h3 class=\"subhead\">Recent orders</h3>" +
|
|
10549
|
-
" <table><thead><tr><th scope=\"col\">Order</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Total</th></tr></thead><tbody>" + recentRows + "</tbody></table>" +
|
|
10559
|
+
" " + _tableWrap("<table><thead><tr><th scope=\"col\">Order</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Total</th></tr></thead><tbody>" + recentRows + "</tbody></table>") +
|
|
10550
10560
|
" </div>" +
|
|
10551
10561
|
"</div></section>";
|
|
10552
10562
|
|
|
@@ -10807,10 +10817,10 @@ function renderAdminIntegrations(opts) {
|
|
|
10807
10817
|
"<section>" +
|
|
10808
10818
|
"<h2>Integrations</h2>" +
|
|
10809
10819
|
"<p class=\"meta\">Every integration is off until you supply its credentials — set them as deployment secrets, then redeploy. Nothing is enabled without your keys.</p>" +
|
|
10810
|
-
"<div class=\"panel\"
|
|
10820
|
+
"<div class=\"panel\">" + _tableWrap("<table>" +
|
|
10811
10821
|
"<thead><tr><th scope=\"col\">Integration</th><th scope=\"col\">Status</th><th scope=\"col\">To enable</th></tr></thead>" +
|
|
10812
10822
|
"<tbody>" + rows + "</tbody>" +
|
|
10813
|
-
"</table
|
|
10823
|
+
"</table>") + "</div>" +
|
|
10814
10824
|
"<p class=\"meta mt-125\">Sign in with Apple and PayPal are planned. “Sign in with Shop” / Shop Pay isn't available to a self-hosted store. See the README “Optional integrations” section for full setup steps.</p>" +
|
|
10815
10825
|
"<div class=\"actions-row\"><a class=\"btn btn--ghost\" href=\"/admin\">Back</a></div>" +
|
|
10816
10826
|
"</section>";
|
|
@@ -10836,7 +10846,7 @@ function renderAdminProducts(opts) {
|
|
|
10836
10846
|
"</div></td></tr>";
|
|
10837
10847
|
}).join("");
|
|
10838
10848
|
var table = products.length
|
|
10839
|
-
? "<div class=\"panel\"
|
|
10849
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Title</th><th scope=\"col\">Slug</th><th scope=\"col\">Status</th><th scope=\"col\">Action</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
10840
10850
|
: "<p class=\"empty\">No products yet — create your first one below.</p>";
|
|
10841
10851
|
var body =
|
|
10842
10852
|
"<section><h2>Products</h2>" + created + notice + table +
|
|
@@ -10892,7 +10902,7 @@ function renderAdminOrders(opts) {
|
|
|
10892
10902
|
}).join("");
|
|
10893
10903
|
|
|
10894
10904
|
var table = orders.length
|
|
10895
|
-
? "<div class=\"panel\"
|
|
10905
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Order</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Items</th><th scope=\"col\" class=\"num\">Total</th><th scope=\"col\">Placed</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
10896
10906
|
: "<p class=\"empty\">No orders" + (active ? " with status “" + _htmlEscape(active) + "”" : " yet") + ".</p>";
|
|
10897
10907
|
|
|
10898
10908
|
var body = "<section><h2>Orders</h2>" + notice + chips + table + "</section>";
|
|
@@ -10964,7 +10974,7 @@ function renderAdminAudit(opts) {
|
|
|
10964
10974
|
}).join("");
|
|
10965
10975
|
|
|
10966
10976
|
var table = rows.length
|
|
10967
|
-
? "<div class=\"panel\"
|
|
10977
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Time</th><th scope=\"col\">Action</th><th scope=\"col\">Outcome</th><th scope=\"col\">Actor</th><th scope=\"col\">Resource</th><th scope=\"col\">Details</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
10968
10978
|
: "<p class=\"empty\">No audit events match.</p>";
|
|
10969
10979
|
|
|
10970
10980
|
// Pager: Newer steps back one page (disabled at offset 0); Older steps
|
|
@@ -11055,7 +11065,7 @@ function renderAdminReports(opts) {
|
|
|
11055
11065
|
}).join("");
|
|
11056
11066
|
var funnelBlock =
|
|
11057
11067
|
"<section><h2>Order status</h2><div class=\"panel\">" +
|
|
11058
|
-
"<table><thead><tr><th scope=\"col\">Stage</th><th scope=\"col\" class=\"num\">Orders</th></tr></thead><tbody>" + funnelRows + "</tbody></table>" +
|
|
11068
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">Stage</th><th scope=\"col\" class=\"num\">Orders</th></tr></thead><tbody>" + funnelRows + "</tbody></table>") +
|
|
11059
11069
|
"</div></section>";
|
|
11060
11070
|
|
|
11061
11071
|
// Top products by gross revenue across the window.
|
|
@@ -11069,7 +11079,7 @@ function renderAdminReports(opts) {
|
|
|
11069
11079
|
: "<tr><td colspan=\"3\" class=\"empty\">No sales in this window.</td></tr>";
|
|
11070
11080
|
var topBlock =
|
|
11071
11081
|
"<section><h2>Top products</h2><div class=\"panel\">" +
|
|
11072
|
-
"<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Units</th><th scope=\"col\" class=\"num\">Revenue</th></tr></thead><tbody>" + topRows + "</tbody></table>" +
|
|
11082
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Units</th><th scope=\"col\" class=\"num\">Revenue</th></tr></thead><tbody>" + topRows + "</tbody></table>") +
|
|
11073
11083
|
"</div></section>";
|
|
11074
11084
|
|
|
11075
11085
|
// By-day revenue series — one row per (day, currency) bucket.
|
|
@@ -11088,7 +11098,7 @@ function renderAdminReports(opts) {
|
|
|
11088
11098
|
: "<tr><td colspan=\"6\" class=\"empty\">No sales in this window.</td></tr>";
|
|
11089
11099
|
var dayBlock =
|
|
11090
11100
|
"<section><h2>By day</h2><div class=\"panel\">" +
|
|
11091
|
-
"<table><thead><tr><th scope=\"col\">Date</th><th scope=\"col\">Currency</th><th scope=\"col\" class=\"num\">Orders</th><th scope=\"col\" class=\"num\">Gross</th><th scope=\"col\" class=\"num\">Net</th><th scope=\"col\" class=\"num\">Refunds</th></tr></thead><tbody>" + dayRows + "</tbody></table>" +
|
|
11101
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">Date</th><th scope=\"col\">Currency</th><th scope=\"col\" class=\"num\">Orders</th><th scope=\"col\" class=\"num\">Gross</th><th scope=\"col\" class=\"num\">Net</th><th scope=\"col\" class=\"num\">Refunds</th></tr></thead><tbody>" + dayRows + "</tbody></table>") +
|
|
11092
11102
|
"</div></section>";
|
|
11093
11103
|
|
|
11094
11104
|
var body =
|
|
@@ -11180,7 +11190,7 @@ function renderAdminExports(opts) {
|
|
|
11180
11190
|
"</tr>";
|
|
11181
11191
|
}).join("");
|
|
11182
11192
|
var jobTable = exportsRows.length
|
|
11183
|
-
? "<div class=\"panel\"
|
|
11193
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Format</th><th scope=\"col\">Window</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Rows</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + jobRows + "</tbody></table>") + "</div>"
|
|
11184
11194
|
: "<p class=\"empty\">No scheduled exports yet.</p>";
|
|
11185
11195
|
|
|
11186
11196
|
var scheduleFmtOptions = formats.map(function (f) {
|
|
@@ -11283,7 +11293,7 @@ function renderAdminShippingLabels(opts) {
|
|
|
11283
11293
|
|
|
11284
11294
|
var rows = labels.map(_shippingLabelRow).join("");
|
|
11285
11295
|
var table = labels.length
|
|
11286
|
-
? "<div class=\"panel\"
|
|
11296
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Carrier</th><th scope=\"col\">Tracking</th><th scope=\"col\" class=\"num\">Cost</th><th scope=\"col\">Broker</th><th scope=\"col\">Status</th><th scope=\"col\">Label</th><th scope=\"col\">Created</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
11287
11297
|
: "<p class=\"empty\">No " + _htmlEscape(status) + " labels" + (status === "voided" ? " in this window" : "") + ".</p>";
|
|
11288
11298
|
|
|
11289
11299
|
var body =
|
|
@@ -11305,7 +11315,7 @@ function renderAdminShippingLabelsPending(opts) {
|
|
|
11305
11315
|
var notice = opts.notice ? "<div class=\"banner banner--warn\">" + _htmlEscape(opts.notice) + "</div>" : "";
|
|
11306
11316
|
var rows = labels.map(_shippingLabelRow).join("");
|
|
11307
11317
|
var table = labels.length
|
|
11308
|
-
? "<div class=\"panel\"
|
|
11318
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Carrier</th><th scope=\"col\">Tracking</th><th scope=\"col\" class=\"num\">Cost</th><th scope=\"col\">Broker</th><th scope=\"col\">Status</th><th scope=\"col\">Label</th><th scope=\"col\">Created</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
11309
11319
|
: "<p class=\"empty\">No labels awaiting a broker mint.</p>";
|
|
11310
11320
|
var body =
|
|
11311
11321
|
"<section><h2>Shipping labels</h2>" + notice +
|
|
@@ -11361,7 +11371,7 @@ function renderAdminShippingLabelCosts(opts) {
|
|
|
11361
11371
|
: "<tr><td colspan=\"4\" class=\"empty\">No labels purchased in this window.</td></tr>";
|
|
11362
11372
|
var brokerBlock =
|
|
11363
11373
|
"<section><h2>By broker</h2><div class=\"panel\">" +
|
|
11364
|
-
"<table><thead><tr><th scope=\"col\">Broker</th><th scope=\"col\">Currency</th><th scope=\"col\" class=\"num\">Labels</th><th scope=\"col\" class=\"num\">Spend</th></tr></thead><tbody>" + brokerRows + "</tbody></table>" +
|
|
11374
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">Broker</th><th scope=\"col\">Currency</th><th scope=\"col\" class=\"num\">Labels</th><th scope=\"col\" class=\"num\">Spend</th></tr></thead><tbody>" + brokerRows + "</tbody></table>") +
|
|
11365
11375
|
"</div></section>";
|
|
11366
11376
|
|
|
11367
11377
|
var body =
|
|
@@ -11394,7 +11404,7 @@ function renderAdminOrder(opts) {
|
|
|
11394
11404
|
"</tr>";
|
|
11395
11405
|
}).join("");
|
|
11396
11406
|
var linesTable = (o.lines && o.lines.length)
|
|
11397
|
-
? "<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Qty</th><th scope=\"col\" class=\"num\">Unit</th><th scope=\"col\" class=\"num\">Line</th></tr></thead><tbody>" + lineRows + "</tbody></table>"
|
|
11407
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Qty</th><th scope=\"col\" class=\"num\">Unit</th><th scope=\"col\" class=\"num\">Line</th></tr></thead><tbody>" + lineRows + "</tbody></table>")
|
|
11398
11408
|
: "<p class=\"empty\">No line items recorded.</p>";
|
|
11399
11409
|
|
|
11400
11410
|
function _total(label, minor, strong) {
|
|
@@ -11482,7 +11492,7 @@ function renderAdminOrder(opts) {
|
|
|
11482
11492
|
"<td>" + _htmlEscape(_fmtDate(e.occurred_at)) + "</td></tr>";
|
|
11483
11493
|
}).join("");
|
|
11484
11494
|
var eventsTable = (s.events && s.events.length)
|
|
11485
|
-
? "<table><thead><tr><th scope=\"col\">Status</th><th scope=\"col\">Location</th><th scope=\"col\">When</th></tr></thead><tbody>" + eventRows + "</tbody></table>"
|
|
11495
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Status</th><th scope=\"col\">Location</th><th scope=\"col\">When</th></tr></thead><tbody>" + eventRows + "</tbody></table>")
|
|
11486
11496
|
: "<p class=\"empty\">No carrier events yet.</p>";
|
|
11487
11497
|
var statusOpts = statuses.map(function (st) {
|
|
11488
11498
|
return "<option value=\"" + _htmlEscape(st) + "\">" + _htmlEscape(st) + "</option>";
|
|
@@ -11608,7 +11618,7 @@ function _orderLabelPanel(orderId, shipment, carriers, packageTypes, purchasedVi
|
|
|
11608
11618
|
"</tr>";
|
|
11609
11619
|
}).join("");
|
|
11610
11620
|
var labelsTable = labels.length
|
|
11611
|
-
? "<table><thead><tr><th scope=\"col\">Carrier</th><th scope=\"col\">Tracking</th><th scope=\"col\" class=\"num\">Cost</th><th scope=\"col\">Status</th><th scope=\"col\">Label</th><th scope=\"col\"></th></tr></thead><tbody>" + labelRows + "</tbody></table>"
|
|
11621
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Carrier</th><th scope=\"col\">Tracking</th><th scope=\"col\" class=\"num\">Cost</th><th scope=\"col\">Status</th><th scope=\"col\">Label</th><th scope=\"col\"></th></tr></thead><tbody>" + labelRows + "</tbody></table>")
|
|
11612
11622
|
: "<p class=\"empty\">No labels recorded for this shipment.</p>";
|
|
11613
11623
|
var carrierOpts = carriers.map(function (c) {
|
|
11614
11624
|
return "<option value=\"" + _htmlEscape(c) + "\">" + _htmlEscape(c) + "</option>";
|
|
@@ -11687,7 +11697,7 @@ function _orderSplitPanel(o, plans) {
|
|
|
11687
11697
|
? "<form method=\"post\" action=\"/admin/orders/" + _htmlEscape(o.id) + "/split/plan\" class=\"return-action\">" +
|
|
11688
11698
|
"<h4>Plan a manual split</h4>" +
|
|
11689
11699
|
"<p class=\"meta\">Assign each line to a parcel number (lines sharing a number ship together). Every unit must land in a parcel.</p>" +
|
|
11690
|
-
"<table><thead><tr><th scope=\"col\">Line (order qty)</th><th scope=\"col\">Parcel</th><th scope=\"col\">Qty</th></tr></thead><tbody>" + lineRows + "</tbody></table>" +
|
|
11700
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">Line (order qty)</th><th scope=\"col\">Parcel</th><th scope=\"col\">Qty</th></tr></thead><tbody>" + lineRows + "</tbody></table>") +
|
|
11691
11701
|
"<button class=\"btn\" type=\"submit\">Propose split</button>" +
|
|
11692
11702
|
"</form>"
|
|
11693
11703
|
: "<p class=\"empty\">No order lines to split.</p>";
|
|
@@ -11720,7 +11730,7 @@ function renderAdminPickLists(opts) {
|
|
|
11720
11730
|
"</tr>";
|
|
11721
11731
|
}).join("");
|
|
11722
11732
|
var table = lists.length
|
|
11723
|
-
? "<table><thead><tr><th scope=\"col\">List</th><th scope=\"col\">Location</th><th scope=\"col\">Status</th><th scope=\"col\">Sort</th><th scope=\"col\">Generated</th></tr></thead><tbody>" + rows + "</tbody></table>"
|
|
11733
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">List</th><th scope=\"col\">Location</th><th scope=\"col\">Status</th><th scope=\"col\">Sort</th><th scope=\"col\">Generated</th></tr></thead><tbody>" + rows + "</tbody></table>")
|
|
11724
11734
|
: "<p class=\"empty\">No pick lists yet. Generate one from the open orders at a location.</p>";
|
|
11725
11735
|
|
|
11726
11736
|
var statusFilter = "<form method=\"get\" action=\"/admin/pick-lists\" class=\"form-inline\">" +
|
|
@@ -11807,7 +11817,7 @@ function renderAdminPickList(opts) {
|
|
|
11807
11817
|
"</tr>";
|
|
11808
11818
|
}).join("");
|
|
11809
11819
|
var linesTable = (l.lines && l.lines.length)
|
|
11810
|
-
? "<table><thead><tr><th scope=\"col\">Aisle/bin</th><th scope=\"col\">SKU</th><th scope=\"col\">Order</th><th scope=\"col\" class=\"num\">Expected</th><th scope=\"col\">Picked</th><th scope=\"col\"></th></tr></thead><tbody>" + lineRows + "</tbody></table>"
|
|
11820
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Aisle/bin</th><th scope=\"col\">SKU</th><th scope=\"col\">Order</th><th scope=\"col\" class=\"num\">Expected</th><th scope=\"col\">Picked</th><th scope=\"col\"></th></tr></thead><tbody>" + lineRows + "</tbody></table>")
|
|
11811
11821
|
: "<p class=\"empty\">This worksheet has no lines.</p>";
|
|
11812
11822
|
|
|
11813
11823
|
// Variance summary — short / over picks only (the discrepancies feed
|
|
@@ -11822,7 +11832,7 @@ function renderAdminPickList(opts) {
|
|
|
11822
11832
|
}).join("");
|
|
11823
11833
|
var variancePanel = discRows
|
|
11824
11834
|
? "<div class=\"panel mt\"><h3 class=\"subhead\">Variances</h3>" +
|
|
11825
|
-
"<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Expected</th><th scope=\"col\" class=\"num\">Picked</th><th scope=\"col\" class=\"num\">Diff</th></tr></thead><tbody>" + discRows + "</tbody></table
|
|
11835
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Expected</th><th scope=\"col\" class=\"num\">Picked</th><th scope=\"col\" class=\"num\">Diff</th></tr></thead><tbody>" + discRows + "</tbody></table>") + "</div>"
|
|
11826
11836
|
: "";
|
|
11827
11837
|
|
|
11828
11838
|
// Complete / cancel actions — only on a non-terminal worksheet.
|
|
@@ -11946,7 +11956,7 @@ function renderAdminCustomers(opts) {
|
|
|
11946
11956
|
var notice = opts.notice ? "<div class=\"banner banner--warn\">" + _htmlEscape(opts.notice) + "</div>" : "";
|
|
11947
11957
|
|
|
11948
11958
|
var table = customers.length
|
|
11949
|
-
? "<div class=\"panel\"
|
|
11959
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Name</th><th scope=\"col\">ID</th><th scope=\"col\">Joined</th><th scope=\"col\">Sign-in method</th><th scope=\"col\" class=\"num\">Orders</th><th scope=\"col\"><span class=\"sr-only\">Manage</span></th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
11950
11960
|
: "<p class=\"empty\">No customers yet.</p>";
|
|
11951
11961
|
|
|
11952
11962
|
// Cursor pager — a Next link when the page filled and more rows remain.
|
|
@@ -11997,7 +12007,7 @@ function renderAdminCustomerDetail(opts) {
|
|
|
11997
12007
|
}).join("");
|
|
11998
12008
|
var ordersPanel = "<div class=\"panel\"><h3 class=\"subhead\">Recent orders</h3>" +
|
|
11999
12009
|
((opts.recent_orders || []).length
|
|
12000
|
-
? "<table><thead><tr><th scope=\"col\">Order</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Total</th><th scope=\"col\">Placed</th></tr></thead><tbody>" + orderRows + "</tbody></table>"
|
|
12010
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Order</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Total</th><th scope=\"col\">Placed</th></tr></thead><tbody>" + orderRows + "</tbody></table>")
|
|
12001
12011
|
: "<p class=\"empty\">No orders yet.</p>") +
|
|
12002
12012
|
"</div>";
|
|
12003
12013
|
|
|
@@ -12021,7 +12031,7 @@ function renderAdminCustomerDetail(opts) {
|
|
|
12021
12031
|
"</tr>";
|
|
12022
12032
|
}).join("");
|
|
12023
12033
|
var histTable = (opts.store_credit_history || []).length
|
|
12024
|
-
? "<table><thead><tr><th scope=\"col\">Kind</th><th scope=\"col\" class=\"num\">Amount</th><th scope=\"col\" class=\"num\">Balance after</th><th scope=\"col\">Reason</th><th scope=\"col\">When</th></tr></thead><tbody>" + histRows + "</tbody></table>"
|
|
12034
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Kind</th><th scope=\"col\" class=\"num\">Amount</th><th scope=\"col\" class=\"num\">Balance after</th><th scope=\"col\">Reason</th><th scope=\"col\">When</th></tr></thead><tbody>" + histRows + "</tbody></table>")
|
|
12025
12035
|
: "<p class=\"empty\">No store-credit activity yet.</p>";
|
|
12026
12036
|
creditPanel = "<div class=\"panel\"><h3 class=\"subhead\">Store credit</h3>" +
|
|
12027
12037
|
creditNotice +
|
|
@@ -12321,7 +12331,7 @@ function renderAdminReturns(opts) {
|
|
|
12321
12331
|
}).join("");
|
|
12322
12332
|
|
|
12323
12333
|
var table = rmas.length
|
|
12324
|
-
? "<div class=\"panel\"
|
|
12334
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">RMA</th><th scope=\"col\">Order</th><th scope=\"col\">Reason</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Items</th><th scope=\"col\" class=\"num\">Refund</th><th scope=\"col\">Requested</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
12325
12335
|
: "<p class=\"empty\">No “" + _htmlEscape(active) + "” returns.</p>";
|
|
12326
12336
|
|
|
12327
12337
|
var body = "<section><h2>Returns</h2>" + notice + chips + table + "</section>";
|
|
@@ -12341,7 +12351,7 @@ function renderAdminReturn(opts) {
|
|
|
12341
12351
|
"<td>" + _htmlEscape(l.reason || "—") + "</td></tr>";
|
|
12342
12352
|
}).join("");
|
|
12343
12353
|
var linesTable = (r.lines && r.lines.length)
|
|
12344
|
-
? "<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Qty</th><th scope=\"col\">Reason</th></tr></thead><tbody>" + lineRows + "</tbody></table>"
|
|
12354
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">Qty</th><th scope=\"col\">Reason</th></tr></thead><tbody>" + lineRows + "</tbody></table>")
|
|
12345
12355
|
: "<p class=\"empty\">No line items recorded.</p>";
|
|
12346
12356
|
|
|
12347
12357
|
function _field(label, value) {
|
|
@@ -12483,7 +12493,7 @@ function renderAdminSupport(opts) {
|
|
|
12483
12493
|
}).join("");
|
|
12484
12494
|
|
|
12485
12495
|
var table = tickets.length
|
|
12486
|
-
? "<div class=\"panel\"
|
|
12496
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Subject</th><th scope=\"col\">Category</th><th scope=\"col\">Status</th><th scope=\"col\">Priority</th><th scope=\"col\">Assignee</th><th scope=\"col\">Opened</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
12487
12497
|
: "<p class=\"empty\">No “" + _htmlEscape(active) + "” tickets.</p>";
|
|
12488
12498
|
|
|
12489
12499
|
var body = "<section><h2>Support</h2>" + notice + chips + table + "</section>";
|
|
@@ -12641,7 +12651,7 @@ function renderAdminDsr(opts) {
|
|
|
12641
12651
|
}).join("");
|
|
12642
12652
|
|
|
12643
12653
|
var table = requests.length
|
|
12644
|
-
? "<div class=\"panel\"
|
|
12654
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Customer</th><th scope=\"col\">Kind</th><th scope=\"col\">Jurisdiction</th><th scope=\"col\">Scope</th><th scope=\"col\">Status</th><th scope=\"col\">Requested</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
12645
12655
|
: "<p class=\"empty\">No “" + _htmlEscape(active) + "” privacy requests.</p>";
|
|
12646
12656
|
|
|
12647
12657
|
var body = "<section><h2>Privacy requests</h2>" +
|
|
@@ -12724,7 +12734,7 @@ function renderAdminDsrDetail(opts) {
|
|
|
12724
12734
|
previewHtml =
|
|
12725
12735
|
"<div class=\"panel mt\"><h3 class=\"subhead\">Erasure preview (dry run — nothing was changed)</h3>" +
|
|
12726
12736
|
(dRows
|
|
12727
|
-
? "<table><thead><tr><th scope=\"col\">Table</th><th scope=\"col\">Affected</th></tr></thead><tbody>" + dRows + "</tbody></table>"
|
|
12737
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Table</th><th scope=\"col\">Affected</th></tr></thead><tbody>" + dRows + "</tbody></table>")
|
|
12728
12738
|
: "<p class=\"empty\">No per-domain handlers wired.</p>") +
|
|
12729
12739
|
(absent ? "<p class=\"meta\">Domains without a deletion handler: " + absent + "</p>" : "") +
|
|
12730
12740
|
"<p class=\"meta\">Total affected: " + _htmlEscape(String(preview.total_affected)) + "</p>" +
|
|
@@ -12740,7 +12750,7 @@ function renderAdminDsrDetail(opts) {
|
|
|
12740
12750
|
"</tr>";
|
|
12741
12751
|
}).join("");
|
|
12742
12752
|
var historyHtml = history.length
|
|
12743
|
-
? "<table><thead><tr><th scope=\"col\">Request</th><th scope=\"col\">Kind</th><th scope=\"col\">Status</th><th scope=\"col\">Requested</th></tr></thead><tbody>" + histRows + "</tbody></table>"
|
|
12753
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Request</th><th scope=\"col\">Kind</th><th scope=\"col\">Status</th><th scope=\"col\">Requested</th></tr></thead><tbody>" + histRows + "</tbody></table>")
|
|
12744
12754
|
: "<p class=\"empty\">No prior requests.</p>";
|
|
12745
12755
|
|
|
12746
12756
|
var body =
|
|
@@ -12814,7 +12824,7 @@ function renderAdminExchanges(opts) {
|
|
|
12814
12824
|
}).join("");
|
|
12815
12825
|
|
|
12816
12826
|
var table = list.length
|
|
12817
|
-
? "<div class=\"panel\"
|
|
12827
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Exchange</th><th scope=\"col\">Order</th><th scope=\"col\">Swap</th><th scope=\"col\">Reason</th><th scope=\"col\">Status</th><th scope=\"col\">Requested</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
12818
12828
|
: "<p class=\"empty\">No “" + _htmlEscape(active) + "” exchanges.</p>";
|
|
12819
12829
|
|
|
12820
12830
|
var body = "<section><h2>Exchanges</h2>" + notice + chips + table + "</section>";
|
|
@@ -12962,7 +12972,7 @@ function renderAdminPickupLocations(opts) {
|
|
|
12962
12972
|
}).join("");
|
|
12963
12973
|
|
|
12964
12974
|
var table = list.length
|
|
12965
|
-
? "<div class=\"panel\"
|
|
12975
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Code</th><th scope=\"col\">Name</th><th scope=\"col\">Address</th><th scope=\"col\">Capacity</th><th scope=\"col\">Lead time</th><th scope=\"col\">Action</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
12966
12976
|
: "<p class=\"empty\">No active pickup locations yet. Add one below.</p>";
|
|
12967
12977
|
|
|
12968
12978
|
var form =
|
|
@@ -13058,7 +13068,7 @@ function renderAdminPickups(opts) {
|
|
|
13058
13068
|
var table = !selected
|
|
13059
13069
|
? "<p class=\"empty\">Add a pickup location first.</p>"
|
|
13060
13070
|
: (list.length
|
|
13061
|
-
? "<div class=\"panel\"
|
|
13071
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Order</th><th scope=\"col\">Status</th><th scope=\"col\">Window</th><th scope=\"col\">Picked up</th><th scope=\"col\">Action</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
13062
13072
|
: "<p class=\"empty\">No pickups in this window for this location.</p>");
|
|
13063
13073
|
|
|
13064
13074
|
var body = "<section><h2>Pickups</h2>" + moved + notice + selector + chips + table + "</section>";
|
|
@@ -13104,7 +13114,7 @@ function renderAdminGiftWraps(opts) {
|
|
|
13104
13114
|
}).join("");
|
|
13105
13115
|
|
|
13106
13116
|
var table = list.length
|
|
13107
|
-
? "<div class=\"panel\"
|
|
13117
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\">Image</th><th scope=\"col\">Title</th><th scope=\"col\">Fee</th><th scope=\"col\">State</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
13108
13118
|
: "<p class=\"empty\">No gift wraps yet. Define one below — the SKU must be a real catalog variant.</p>";
|
|
13109
13119
|
|
|
13110
13120
|
var form =
|
|
@@ -13443,7 +13453,7 @@ function renderAdminInventory(opts) {
|
|
|
13443
13453
|
}).join("");
|
|
13444
13454
|
|
|
13445
13455
|
var table = rows.length
|
|
13446
|
-
? "<div class=\"panel\"
|
|
13456
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">SKU</th><th scope=\"col\" class=\"num\">On hand</th><th scope=\"col\" class=\"num\">Held</th><th scope=\"col\" class=\"num\">Available</th><th scope=\"col\">Restock / threshold</th></tr></thead><tbody>" + body + "</tbody></table>") + "</div>"
|
|
13447
13457
|
: "<p class=\"empty\">No inventory rows" + (opts.low ? " below threshold" : " yet") + ".</p>";
|
|
13448
13458
|
|
|
13449
13459
|
var createForm =
|
|
@@ -13550,7 +13560,7 @@ function renderAdminSubscriptionPlans(opts) {
|
|
|
13550
13560
|
}).join("");
|
|
13551
13561
|
|
|
13552
13562
|
var table = rows.length
|
|
13553
|
-
? "<div class=\"panel\"
|
|
13563
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Price / interval</th><th scope=\"col\">Stripe price</th><th scope=\"col\">Variant</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
13554
13564
|
: "<p class=\"empty\">No subscription plans" + (af === "0" ? " archived" : af === "1" ? " active" : " yet") + ".</p>";
|
|
13555
13565
|
|
|
13556
13566
|
var intervalOpts = ["month", "year", "week", "day"].map(function (iv) {
|
|
@@ -13651,7 +13661,7 @@ function renderAdminTaxRates(opts) {
|
|
|
13651
13661
|
"</tr>";
|
|
13652
13662
|
}).join("");
|
|
13653
13663
|
table = rows.length
|
|
13654
|
-
? "<div class=\"panel\"
|
|
13664
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Category</th><th scope=\"col\" class=\"num\">Rate</th><th scope=\"col\">From</th><th scope=\"col\">Until</th><th scope=\"col\">Source</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
13655
13665
|
: "<p class=\"empty\">No rates for " + _htmlEscape(jurisdiction) + " yet.</p>";
|
|
13656
13666
|
}
|
|
13657
13667
|
|
|
@@ -13734,7 +13744,7 @@ function renderAdminTaxFilings(opts) {
|
|
|
13734
13744
|
}).join("");
|
|
13735
13745
|
var dueBlock = upcoming.length
|
|
13736
13746
|
? "<section><h2>Due soon or overdue</h2><div class=\"panel\">" +
|
|
13737
|
-
"<table><thead><tr><th scope=\"col\">Jurisdiction</th><th scope=\"col\">Period</th><th scope=\"col\">Status</th><th scope=\"col\">Due</th></tr></thead><tbody>" + dueRows + "</tbody></table>" +
|
|
13747
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">Jurisdiction</th><th scope=\"col\">Period</th><th scope=\"col\">Status</th><th scope=\"col\">Due</th></tr></thead><tbody>" + dueRows + "</tbody></table>") +
|
|
13738
13748
|
"</div></section>"
|
|
13739
13749
|
: "";
|
|
13740
13750
|
|
|
@@ -13751,7 +13761,7 @@ function renderAdminTaxFilings(opts) {
|
|
|
13751
13761
|
"</tr>";
|
|
13752
13762
|
}).join("");
|
|
13753
13763
|
var table = filings.length
|
|
13754
|
-
? "<div class=\"panel\"
|
|
13764
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Jurisdiction</th><th scope=\"col\">Kind</th><th scope=\"col\">Period</th><th scope=\"col\">Due</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Collected</th><th scope=\"col\" class=\"num\">Owed</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
13755
13765
|
: "<p class=\"empty\">No filings" + (jurisdiction ? " for " + _htmlEscape(jurisdiction) : "") + " yet. Open a period below.</p>";
|
|
13756
13766
|
|
|
13757
13767
|
// Open-a-period form.
|
|
@@ -13841,7 +13851,7 @@ function renderAdminTaxFiling(opts) {
|
|
|
13841
13851
|
"</tr>";
|
|
13842
13852
|
}).join("");
|
|
13843
13853
|
var breakdownBlock = bkeys.length
|
|
13844
|
-
? "<section><h2>By rate</h2><div class=\"panel\"
|
|
13854
|
+
? "<section><h2>By rate</h2><div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Rate</th><th scope=\"col\" class=\"num\">Orders</th><th scope=\"col\" class=\"num\">Taxable</th><th scope=\"col\" class=\"num\">Tax</th></tr></thead><tbody>" + breakdownRows + "</tbody></table>") + "</div></section>"
|
|
13845
13855
|
: "";
|
|
13846
13856
|
|
|
13847
13857
|
// Audit trail — submission + payment + amendment columns once recorded.
|
|
@@ -13862,7 +13872,7 @@ function renderAdminTaxFiling(opts) {
|
|
|
13862
13872
|
trailRows += "<tr><td>Amended</td><td>" + _htmlEscape(f.amended_reason) +
|
|
13863
13873
|
(f.amended_at != null ? " <span class=\"meta\">" + _htmlEscape(_fmtDate(f.amended_at)) + "</span>" : "") + "</td></tr>";
|
|
13864
13874
|
}
|
|
13865
|
-
trail = "<section><h2>Audit trail</h2><div class=\"panel\"
|
|
13875
|
+
trail = "<section><h2>Audit trail</h2><div class=\"panel\">" + _tableWrap("<table><tbody>" + trailRows + "</tbody></table>") + "</div></section>";
|
|
13866
13876
|
}
|
|
13867
13877
|
|
|
13868
13878
|
// Lifecycle actions — gated on the FSM. draft → compute; computed →
|
|
@@ -13937,7 +13947,7 @@ function renderAdminTaxFilingReport(opts) {
|
|
|
13937
13947
|
"</tr>";
|
|
13938
13948
|
}).join("");
|
|
13939
13949
|
var table = (report.filings || []).length
|
|
13940
|
-
? "<div class=\"panel\"
|
|
13950
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Period</th><th scope=\"col\">Window</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Collected</th><th scope=\"col\" class=\"num\">Owed</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
13941
13951
|
: "<p class=\"empty\">No filings intersect this window.</p>";
|
|
13942
13952
|
|
|
13943
13953
|
var body =
|
|
@@ -13981,7 +13991,7 @@ function renderAdminShipping(opts) {
|
|
|
13981
13991
|
}).join("");
|
|
13982
13992
|
|
|
13983
13993
|
var table = rows.length
|
|
13984
|
-
? "<div class=\"panel\"
|
|
13994
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Title</th><th scope=\"col\">Slug</th><th scope=\"col\">Regions</th><th scope=\"col\" class=\"num\">Rates</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
13985
13995
|
: "<p class=\"empty\">No shipping zones yet.</p>";
|
|
13986
13996
|
|
|
13987
13997
|
var createForm =
|
|
@@ -14031,7 +14041,7 @@ function renderAdminShippingZone(opts) {
|
|
|
14031
14041
|
"</tr>";
|
|
14032
14042
|
}).join("");
|
|
14033
14043
|
var rateTable = (z.rates || []).length
|
|
14034
|
-
? "<div class=\"panel\"
|
|
14044
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Service</th><th scope=\"col\" class=\"num\">Rate</th><th scope=\"col\">Bucket</th></tr></thead><tbody>" + rateRows + "</tbody></table>") + "</div>"
|
|
14035
14045
|
: "<p class=\"empty\">No rate rows.</p>";
|
|
14036
14046
|
|
|
14037
14047
|
var head = "<p class=\"meta\"><a href=\"/admin/shipping\">← Shipping</a> · <code class=\"order-id\">" + _htmlEscape(z.slug) + "</code> · " +
|
|
@@ -14193,7 +14203,7 @@ function renderAdminDiscounts(opts) {
|
|
|
14193
14203
|
"</tr>";
|
|
14194
14204
|
}).join("");
|
|
14195
14205
|
var ruleTable = rules.length
|
|
14196
|
-
? "<div class=\"panel\"
|
|
14206
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Rule</th><th scope=\"col\">Trigger</th><th scope=\"col\">Value</th><th scope=\"col\" class=\"num\">Priority</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + ruleRows + "</tbody></table>") + "</div>"
|
|
14197
14207
|
: "<p class=\"empty\">No automatic discount rules yet.</p>";
|
|
14198
14208
|
|
|
14199
14209
|
var TRIGGERS = [
|
|
@@ -14256,7 +14266,7 @@ function renderAdminDiscounts(opts) {
|
|
|
14256
14266
|
"</tr>";
|
|
14257
14267
|
}).join("");
|
|
14258
14268
|
var polTable = policies.length
|
|
14259
|
-
? "<div class=\"panel\"
|
|
14269
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Policy</th><th scope=\"col\" class=\"num\">Max codes</th><th scope=\"col\">Combines with</th><th scope=\"col\" class=\"num\">Order min</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + polRows + "</tbody></table>") + "</div>"
|
|
14260
14270
|
: "<p class=\"empty\">No stacking policies yet — without one, only one code applies per order.</p>";
|
|
14261
14271
|
var polForm =
|
|
14262
14272
|
"<div class=\"panel mt mw-40\">" +
|
|
@@ -14362,7 +14372,7 @@ function renderAdminSegments(opts) {
|
|
|
14362
14372
|
}).join("");
|
|
14363
14373
|
|
|
14364
14374
|
var table = segments.length
|
|
14365
|
-
? "<div class=\"panel\"
|
|
14375
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Segment</th><th scope=\"col\">Rules</th><th scope=\"col\" class=\"num\">Members</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
14366
14376
|
: "<p class=\"empty\">No " + (filter === "archived" ? "archived " : (filter === "all" ? "" : "active ")) + "segments yet. Define one to start grouping customers for campaigns.</p>";
|
|
14367
14377
|
|
|
14368
14378
|
var body = "<section><h2>Customer segments</h2>" + created + updated + archived + unarchived + recomputed + notice +
|
|
@@ -14504,7 +14514,7 @@ function renderAdminGiftCards(opts) {
|
|
|
14504
14514
|
}).join("");
|
|
14505
14515
|
|
|
14506
14516
|
var table = rows.length
|
|
14507
|
-
? "<div class=\"panel\"
|
|
14517
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Code</th><th scope=\"col\" class=\"num\">Issued</th><th scope=\"col\" class=\"num\">Remaining</th><th scope=\"col\">Status</th><th scope=\"col\">Issued on</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
14508
14518
|
: "<p class=\"empty\">No gift cards" + (sf ? " " + _htmlEscape(sf) : " yet") + ".</p>";
|
|
14509
14519
|
|
|
14510
14520
|
// The issue form composes the giftcards primitive's issue() — the
|
|
@@ -14571,7 +14581,7 @@ function renderAdminGiftCard(opts) {
|
|
|
14571
14581
|
"</tr>";
|
|
14572
14582
|
}).join("");
|
|
14573
14583
|
var ledgerTable = ledger.length
|
|
14574
|
-
? "<div class=\"panel\"
|
|
14584
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Type</th><th scope=\"col\" class=\"num\">Amount</th><th scope=\"col\" class=\"num\">Balance after</th><th scope=\"col\">Detail</th><th scope=\"col\">When</th></tr></thead><tbody>" + ledgerRows + "</tbody></table>") + "</div>"
|
|
14575
14585
|
: "<p class=\"empty\">No ledger transactions recorded for this card yet.</p>";
|
|
14576
14586
|
|
|
14577
14587
|
// Void is offered only while the card is active — a redeemed card has
|
|
@@ -14613,7 +14623,7 @@ function renderAdminWebhooks(opts) {
|
|
|
14613
14623
|
}).join("");
|
|
14614
14624
|
|
|
14615
14625
|
var table = rows.length
|
|
14616
|
-
? "<div class=\"panel\"
|
|
14626
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">URL</th><th scope=\"col\">Events</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Rate</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
14617
14627
|
: "<p class=\"empty\">No webhook endpoints yet.</p>";
|
|
14618
14628
|
|
|
14619
14629
|
var eventChecks = known.map(function (ev) {
|
|
@@ -14690,7 +14700,7 @@ function renderAdminWebhookDeliveries(opts) {
|
|
|
14690
14700
|
}).join("");
|
|
14691
14701
|
|
|
14692
14702
|
var table = rows.length
|
|
14693
|
-
? "<div class=\"panel\"
|
|
14703
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Event</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Code</th><th scope=\"col\" class=\"num\">Attempts</th><th scope=\"col\">Last error</th><th scope=\"col\">Created</th><th scope=\"col\"></th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
14694
14704
|
: "<p class=\"empty\">No deliveries recorded for this endpoint yet.</p>";
|
|
14695
14705
|
|
|
14696
14706
|
var head = "<p class=\"meta\">Endpoint <code class=\"order-id\">" + _htmlEscape(e.url) + "</code></p>";
|
|
@@ -14734,7 +14744,7 @@ function renderAdminCollections(opts) {
|
|
|
14734
14744
|
}).join("");
|
|
14735
14745
|
|
|
14736
14746
|
var table = rows.length
|
|
14737
|
-
? "<div class=\"panel\"
|
|
14747
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Title</th><th scope=\"col\">Slug</th><th scope=\"col\">Type</th><th scope=\"col\">Status</th><th scope=\"col\" class=\"num\">Products</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
14738
14748
|
: "<p class=\"empty\">No collections" + (af === "0" ? " archived" : af === "1" ? " active" : " yet") + ".</p>";
|
|
14739
14749
|
|
|
14740
14750
|
// The create form toggles between a manual and a smart shape. Manual
|
|
@@ -14813,7 +14823,7 @@ function renderAdminDiscountAllocation(opts) {
|
|
|
14813
14823
|
"</tr>";
|
|
14814
14824
|
}).join("");
|
|
14815
14825
|
var lineTable = breakdown.length
|
|
14816
|
-
? "<table><thead><tr><th scope=\"col\">Line</th><th scope=\"col\" class=\"num\">Allocated (minor)</th><th scope=\"col\" class=\"num\">Remaining (minor)</th></tr></thead><tbody>" + lineRows + "</tbody></table>"
|
|
14826
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Line</th><th scope=\"col\" class=\"num\">Allocated (minor)</th><th scope=\"col\" class=\"num\">Remaining (minor)</th></tr></thead><tbody>" + lineRows + "</tbody></table>")
|
|
14817
14827
|
: "<p class=\"empty\">This allocation has no recorded lines.</p>";
|
|
14818
14828
|
return "<div class=\"panel mt\">" +
|
|
14819
14829
|
"<h3 class=\"subhead\">" + _htmlEscape(a.source || "—") + "</h3>" +
|
|
@@ -15125,7 +15135,7 @@ function renderAdminAnnouncements(opts) {
|
|
|
15125
15135
|
}).join("");
|
|
15126
15136
|
|
|
15127
15137
|
var table = rows.length
|
|
15128
|
-
? "<div class=\"panel\"
|
|
15138
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Message</th><th scope=\"col\">Theme</th><th scope=\"col\">Audience</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
15129
15139
|
: "<p class=\"empty\">No announcements" + (af === "1" ? " active right now" : " yet") + ".</p>";
|
|
15130
15140
|
|
|
15131
15141
|
var themeOpts = ["urgency", "promo", "info", "success"].map(function (t) {
|
|
@@ -15199,7 +15209,7 @@ function renderAdminSearchRanking(opts) {
|
|
|
15199
15209
|
"</tr>";
|
|
15200
15210
|
}).join("");
|
|
15201
15211
|
var table = weights.length
|
|
15202
|
-
? "<div class=\"panel\"
|
|
15212
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Name</th><th scope=\"col\">Weights</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
15203
15213
|
: "<p class=\"empty\">No weight sets yet. Create one below and make it active to rerank storefront search.</p>";
|
|
15204
15214
|
|
|
15205
15215
|
var createForm =
|
|
@@ -15234,7 +15244,7 @@ function renderAdminSearchRanking(opts) {
|
|
|
15234
15244
|
"</form>";
|
|
15235
15245
|
var pinTable = pinsQuery
|
|
15236
15246
|
? (pins.length
|
|
15237
|
-
? "<div class=\"panel\"
|
|
15247
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Product id</th><th scope=\"col\">Position</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + pinRows + "</tbody></table>") + "</div>"
|
|
15238
15248
|
: "<p class=\"empty\">No pins for “" + _htmlEscape(pinsQuery) + "” yet.</p>")
|
|
15239
15249
|
: "";
|
|
15240
15250
|
var pinCreateForm = pinsQuery
|
|
@@ -15309,7 +15319,7 @@ function renderAdminTrustBadges(opts) {
|
|
|
15309
15319
|
"</tr>";
|
|
15310
15320
|
}).join("");
|
|
15311
15321
|
var table = badges.length
|
|
15312
|
-
? "<div class=\"panel\"
|
|
15322
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Title</th><th scope=\"col\">Kind</th><th scope=\"col\">Placements</th><th scope=\"col\">Priority</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + rows + "</tbody></table>") + "</div>"
|
|
15313
15323
|
: "<p class=\"empty\">No trust badges yet. Create one below.</p>";
|
|
15314
15324
|
|
|
15315
15325
|
var placementChecks = trustBadgesModule.ALLOWED_PLACEMENTS.map(function (pl) {
|
|
@@ -15449,11 +15459,11 @@ function renderAdminPreorders(opts) {
|
|
|
15449
15459
|
}).join("");
|
|
15450
15460
|
|
|
15451
15461
|
var table = rows.length
|
|
15452
|
-
? "<div class=\"panel\"
|
|
15462
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr>" +
|
|
15453
15463
|
"<th scope=\"col\">Slug</th><th scope=\"col\">SKU</th><th scope=\"col\">Ships</th>" +
|
|
15454
15464
|
"<th scope=\"col\">Price</th><th scope=\"col\">Reserved / cap</th><th scope=\"col\">Remaining</th>" +
|
|
15455
15465
|
"<th scope=\"col\">Status</th><th scope=\"col\">Actions</th>" +
|
|
15456
|
-
"</tr></thead><tbody>" + bodyRows + "</tbody></table
|
|
15466
|
+
"</tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
15457
15467
|
: "<p class=\"empty\">No pre-order campaigns yet.</p>";
|
|
15458
15468
|
|
|
15459
15469
|
var createForm =
|
|
@@ -15622,7 +15632,7 @@ function renderAdminHelp(opts) {
|
|
|
15622
15632
|
}).join("");
|
|
15623
15633
|
|
|
15624
15634
|
var table = rows.length
|
|
15625
|
-
? "<div class=\"panel\"
|
|
15635
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Title</th><th scope=\"col\">Slug</th><th scope=\"col\">Category</th><th scope=\"col\">Status</th><th scope=\"col\">Views</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
15626
15636
|
: "<p class=\"empty\">No articles" + (sf ? " " + _htmlEscape(sf) : " yet") + ". Write your first one.</p>";
|
|
15627
15637
|
|
|
15628
15638
|
var newBtn = "<div class=\"actions-row\"><a class=\"btn\" href=\"/admin/help/new\">New article</a></div>";
|
|
@@ -15774,7 +15784,7 @@ function renderAdminBlog(opts) {
|
|
|
15774
15784
|
}).join("");
|
|
15775
15785
|
|
|
15776
15786
|
var table = rows.length
|
|
15777
|
-
? "<div class=\"panel\"
|
|
15787
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Title</th><th scope=\"col\">Slug</th><th scope=\"col\">Status</th><th scope=\"col\">Published</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
15778
15788
|
: "<p class=\"empty\">No posts" + (sf ? " " + _htmlEscape(sf) : " yet") + ". Write your first one.</p>";
|
|
15779
15789
|
|
|
15780
15790
|
var newBtn = "<div class=\"actions-row\"><a class=\"btn\" href=\"/admin/blog/new\">New post</a></div>";
|
|
@@ -15975,7 +15985,7 @@ function renderAdminPages(opts) {
|
|
|
15975
15985
|
}).join("");
|
|
15976
15986
|
|
|
15977
15987
|
var table = rows.length
|
|
15978
|
-
? "<div class=\"panel\"
|
|
15988
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Title</th><th scope=\"col\">Slug</th><th scope=\"col\">Status</th><th scope=\"col\">Published</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
15979
15989
|
: "<p class=\"empty\">No pages" + (sf ? " " + _htmlEscape(sf) : " yet") + ". Write your first one.</p>";
|
|
15980
15990
|
|
|
15981
15991
|
var newBtn = "<div class=\"actions-row\"><a class=\"btn\" href=\"/admin/pages/new\">New page</a></div>";
|
|
@@ -16129,7 +16139,7 @@ function renderAdminSurveys(opts) {
|
|
|
16129
16139
|
}).join("");
|
|
16130
16140
|
|
|
16131
16141
|
var table = rows.length
|
|
16132
|
-
? "<div class=\"panel\"
|
|
16142
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Title</th><th scope=\"col\">Slug</th><th scope=\"col\">Kind</th><th scope=\"col\">Trigger</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
16133
16143
|
: "<p class=\"empty\">No surveys" + (af === "1" ? " active" : " yet") + ".</p>";
|
|
16134
16144
|
|
|
16135
16145
|
var kindOpts = [["nps", "NPS — recommend score"], ["csat", "CSAT — satisfaction"], ["ces", "CES — ease of effort"], ["custom", "Custom — open feedback"]]
|
|
@@ -16192,7 +16202,7 @@ function renderAdminSurveyDetail(opts) {
|
|
|
16192
16202
|
var rollupPanel =
|
|
16193
16203
|
"<div class=\"panel\">" +
|
|
16194
16204
|
"<p class=\"meta\">" + _htmlEscape(String(roll.response_count)) + " response(s)." + (headline ? " " + headline : "") + "</p>" +
|
|
16195
|
-
(qRows ? "<table><thead><tr><th scope=\"col\">Question</th><th scope=\"col\">Kind</th><th scope=\"col\">Result</th></tr></thead><tbody>" + qRows + "</tbody></table>" : "") +
|
|
16205
|
+
(qRows ? _tableWrap("<table><thead><tr><th scope=\"col\">Question</th><th scope=\"col\">Kind</th><th scope=\"col\">Result</th></tr></thead><tbody>" + qRows + "</tbody></table>") : "") +
|
|
16196
16206
|
"</div>";
|
|
16197
16207
|
|
|
16198
16208
|
var enc = _htmlEscape(encodeURIComponent(survey.slug));
|
|
@@ -16258,7 +16268,7 @@ function renderAdminHours(opts) {
|
|
|
16258
16268
|
}).join("");
|
|
16259
16269
|
|
|
16260
16270
|
var table = rows.length
|
|
16261
|
-
? "<div class=\"panel\"
|
|
16271
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Schedule</th><th scope=\"col\">Timezone</th><th scope=\"col\" class=\"num\">Days</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
16262
16272
|
: "<p class=\"empty\">No schedules yet.</p>";
|
|
16263
16273
|
|
|
16264
16274
|
var dayFields = DOW.map(function (label, d) {
|
|
@@ -16358,7 +16368,7 @@ function renderAdminCollection(opts) {
|
|
|
16358
16368
|
"</tr>";
|
|
16359
16369
|
}).join("");
|
|
16360
16370
|
var memberTable = members.length
|
|
16361
|
-
? "<div class=\"panel\"
|
|
16371
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\" class=\"num\">#</th><th scope=\"col\">Product id</th><th scope=\"col\"></th></tr></thead><tbody>" + memberRows + "</tbody></table>") + "</div>"
|
|
16362
16372
|
: "<p class=\"empty\">No members yet — add a product below.</p>";
|
|
16363
16373
|
|
|
16364
16374
|
// Reorder: a single field of the current ids, comma-joined, that the
|
|
@@ -16398,7 +16408,7 @@ function renderAdminCollection(opts) {
|
|
|
16398
16408
|
"</tr>";
|
|
16399
16409
|
}).join("");
|
|
16400
16410
|
var previewTable = preview.length
|
|
16401
|
-
? "<div class=\"panel\"
|
|
16411
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Title</th><th scope=\"col\">Slug</th><th scope=\"col\">Status</th></tr></thead><tbody>" + previewCards + "</tbody></table>") + "</div>"
|
|
16402
16412
|
: "<p class=\"empty\">No products match these rules yet.</p>";
|
|
16403
16413
|
detailBody = "<section class=\"mt\"><h3 class=\"fs-105\">Matched products (live preview)</h3>" +
|
|
16404
16414
|
"<p class=\"meta\">The first " + _htmlEscape(String(preview.length)) + " products the rules match right now.</p>" +
|
|
@@ -16490,7 +16500,7 @@ function renderAdminQuantityDiscounts(opts) {
|
|
|
16490
16500
|
}).join("");
|
|
16491
16501
|
|
|
16492
16502
|
var table = rows.length
|
|
16493
|
-
? "<div class=\"panel\"
|
|
16503
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Scope</th><th scope=\"col\">Scope id</th><th scope=\"col\" class=\"num\">Tiers</th><th scope=\"col\">Stacking</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
16494
16504
|
: "<p class=\"empty\">No quantity breaks" + (af === "1" ? " archived" : af === "0" ? " active" : " yet") + ".</p>";
|
|
16495
16505
|
|
|
16496
16506
|
var scopeOpts = scopes.map(function (sc) {
|
|
@@ -16581,7 +16591,7 @@ function renderAdminQuantityDiscount(opts) {
|
|
|
16581
16591
|
"</tr>";
|
|
16582
16592
|
}).join("");
|
|
16583
16593
|
var previewTable = previewRows
|
|
16584
|
-
? "<div class=\"panel\"
|
|
16594
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\" class=\"num\">Min qty</th><th scope=\"col\">Kind</th><th scope=\"col\" class=\"num\">Value</th><th scope=\"col\" class=\"num\">Unit @ min</th><th scope=\"col\" class=\"num\">Line saved</th><th scope=\"col\" class=\"num\">Line total</th></tr></thead><tbody>" + previewRows + "</tbody></table>") + "</div>"
|
|
16585
16595
|
: "<p class=\"empty\">No active rules to preview for this scope.</p>";
|
|
16586
16596
|
var previewForm =
|
|
16587
16597
|
"<form method=\"get\" action=\"/admin/quantity-discounts/" + _htmlEscape(enc) + "\" class=\"actions-row m-04\">" +
|
|
@@ -16664,8 +16674,8 @@ function renderAdminLoyalty(opts) {
|
|
|
16664
16674
|
"<p class=\"meta\">Lifetime points (never decremented) place a customer in a tier. " +
|
|
16665
16675
|
"Earning $1 grants " + _htmlEscape(String(opts.points_per_usd)) + " points; " +
|
|
16666
16676
|
_htmlEscape(String(opts.redemption_points_per_usd)) + " points redeem for $1 of value.</p>" +
|
|
16667
|
-
"<table><thead><tr><th scope=\"col\">Tier</th><th scope=\"col\" class=\"num\">Lifetime points to reach</th></tr></thead>" +
|
|
16668
|
-
"<tbody>" + tierRows + "</tbody></table
|
|
16677
|
+
_tableWrap("<table><thead><tr><th scope=\"col\">Tier</th><th scope=\"col\" class=\"num\">Lifetime points to reach</th></tr></thead>" +
|
|
16678
|
+
"<tbody>" + tierRows + "</tbody></table>") + "</div>";
|
|
16669
16679
|
|
|
16670
16680
|
// Earn-rule summary.
|
|
16671
16681
|
var earnSummary;
|
|
@@ -16686,7 +16696,7 @@ function renderAdminLoyalty(opts) {
|
|
|
16686
16696
|
earnSummary = "<div class=\"panel\"><div class=\"actions-row\"><h3 class=\"subhead\">Earn rules</h3>" +
|
|
16687
16697
|
"<a class=\"btn btn--ghost\" href=\"/admin/loyalty/earn-rules\">Manage earn rules</a></div>" +
|
|
16688
16698
|
(earnRules.length
|
|
16689
|
-
? "<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Trigger</th><th scope=\"col\" class=\"num\">Points/unit</th><th scope=\"col\">Status</th></tr></thead><tbody>" + earnRows + "</tbody></table>"
|
|
16699
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Trigger</th><th scope=\"col\" class=\"num\">Points/unit</th><th scope=\"col\">Status</th></tr></thead><tbody>" + earnRows + "</tbody></table>")
|
|
16690
16700
|
: "<p class=\"empty\">No earn rules yet. Customers only earn points from active rules.</p>") +
|
|
16691
16701
|
"</div>";
|
|
16692
16702
|
}
|
|
@@ -16710,7 +16720,7 @@ function renderAdminLoyalty(opts) {
|
|
|
16710
16720
|
rewardSummary = "<div class=\"panel\"><div class=\"actions-row\"><h3 class=\"subhead\">Rewards catalog</h3>" +
|
|
16711
16721
|
"<a class=\"btn btn--ghost\" href=\"/admin/loyalty/rewards\">Manage rewards</a></div>" +
|
|
16712
16722
|
(rewards.length
|
|
16713
|
-
? "<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Title</th><th scope=\"col\" class=\"num\">Point cost</th><th scope=\"col\">Status</th></tr></thead><tbody>" + rewardRows + "</tbody></table>"
|
|
16723
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Title</th><th scope=\"col\" class=\"num\">Point cost</th><th scope=\"col\">Status</th></tr></thead><tbody>" + rewardRows + "</tbody></table>")
|
|
16714
16724
|
: "<p class=\"empty\">No rewards yet. Customers only see active rewards in their catalog.</p>") +
|
|
16715
16725
|
"</div>";
|
|
16716
16726
|
}
|
|
@@ -16778,7 +16788,7 @@ function renderAdminLoyaltyEarnRules(opts) {
|
|
|
16778
16788
|
"</tr>";
|
|
16779
16789
|
}).join("");
|
|
16780
16790
|
var table = rules.length
|
|
16781
|
-
? "<div class=\"panel\"
|
|
16791
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Trigger</th><th scope=\"col\" class=\"num\">Points/unit</th><th scope=\"col\" class=\"num\">Max/event</th><th scope=\"col\">Statuses</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
16782
16792
|
: "<p class=\"empty\">No earn rules yet.</p>";
|
|
16783
16793
|
|
|
16784
16794
|
var triggerOpts = triggers.map(function (t) {
|
|
@@ -16881,7 +16891,7 @@ function renderAdminLoyaltyRewards(opts) {
|
|
|
16881
16891
|
"</tr>";
|
|
16882
16892
|
}).join("");
|
|
16883
16893
|
var table = rewards.length
|
|
16884
|
-
? "<div class=\"panel\"
|
|
16894
|
+
? "<div class=\"panel\">" + _tableWrap("<table><thead><tr><th scope=\"col\">Slug</th><th scope=\"col\">Title</th><th scope=\"col\">Kind</th><th scope=\"col\">Value</th><th scope=\"col\" class=\"num\">Point cost</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead><tbody>" + bodyRows + "</tbody></table>") + "</div>"
|
|
16885
16895
|
: "<p class=\"empty\">No rewards yet.</p>";
|
|
16886
16896
|
|
|
16887
16897
|
var kindOpts = kinds.map(function (k) {
|
|
@@ -17039,7 +17049,7 @@ function renderAdminProduct(opts) {
|
|
|
17039
17049
|
"</tr>";
|
|
17040
17050
|
}).join("");
|
|
17041
17051
|
var histTable = (pc.history && pc.history.length)
|
|
17042
|
-
? "<table><thead><tr><th scope=\"col\" class=\"num\">Amount</th><th scope=\"col\">From</th><th scope=\"col\">Until</th></tr></thead><tbody>" + histRows + "</tbody></table>"
|
|
17052
|
+
? _tableWrap("<table><thead><tr><th scope=\"col\" class=\"num\">Amount</th><th scope=\"col\">From</th><th scope=\"col\">Until</th></tr></thead><tbody>" + histRows + "</tbody></table>")
|
|
17043
17053
|
: "";
|
|
17044
17054
|
return "<div class=\"m-04\">" +
|
|
17045
17055
|
"<span class=\"u-mute\">" + _htmlEscape(pc.currency) + "</span> · " + current +
|
package/lib/asset-manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.3.
|
|
2
|
+
"version": "0.3.55",
|
|
3
3
|
"assets": {
|
|
4
4
|
"css/admin.css": {
|
|
5
|
-
"integrity": "sha384
|
|
6
|
-
"fingerprinted": "css/admin.
|
|
5
|
+
"integrity": "sha384-6k53cvkRrxMgmeStLIoLjVXZQHqIJgTmv1Izd8TYhh1HOC4POgE6GCvx1bsalyEP",
|
|
6
|
+
"fingerprinted": "css/admin.44eb97700c660798.css"
|
|
7
7
|
},
|
|
8
8
|
"css/main.css": {
|
|
9
|
-
"integrity": "sha384-
|
|
10
|
-
"fingerprinted": "css/main.
|
|
9
|
+
"integrity": "sha384-2nlE5YDlOjcnP/l1EY5kcYwvbV/TboHx+Hi6fAaDNyaau3nzQppJnNyXoLiFE8w1",
|
|
10
|
+
"fingerprinted": "css/main.59afbe779336867a.css"
|
|
11
11
|
},
|
|
12
12
|
"js/announcement.js": {
|
|
13
13
|
"integrity": "sha384-z4zcEMn+tScoVnYRE4nEf8N/oyvpxdpaxTNrT4QO/jURChid4+qjAvWkzatCaAPq",
|
package/lib/storefront.js
CHANGED
|
@@ -2192,6 +2192,49 @@ function _buildPreorderNotice(marker) {
|
|
|
2192
2192
|
return "<p class=\"" + cls + "\" role=\"" + role + "\">" + b.template.escapeHtml(n.copy) + "</p>\n ";
|
|
2193
2193
|
}
|
|
2194
2194
|
|
|
2195
|
+
// Map a thrown validator TypeError to the single form field it rejected, so a
|
|
2196
|
+
// re-render can mark exactly that input with aria-invalid + a per-field error
|
|
2197
|
+
// span (WCAG 3.3.1 Error Identification / 3.3.3 Error Suggestion) — IN ADDITION
|
|
2198
|
+
// to the page-top role="alert" summary banner the routes already render.
|
|
2199
|
+
//
|
|
2200
|
+
// The shop's validators throw "<module>: <field> <reason>" (or
|
|
2201
|
+
// "<module>.<method>: <field> <reason>") where the leading token after the
|
|
2202
|
+
// prefix IS the form `name`. This reads that already-thrown error; it does NOT
|
|
2203
|
+
// re-validate (the backend stays the single validator). `modulePrefix` is the
|
|
2204
|
+
// thrower's prefix (e.g. "addresses", "reviews", "productQA", "supportTickets")
|
|
2205
|
+
// and `formFields` is the set of `name`s the form actually renders, so an error
|
|
2206
|
+
// on a non-form internal (e.g. customer_id) returns null — the page-top banner
|
|
2207
|
+
// still shows, but no input is falsely marked.
|
|
2208
|
+
//
|
|
2209
|
+
// Returns { field, message } with the module prefix stripped from `message`
|
|
2210
|
+
// (so the per-field span carries just the human reason), or null when the
|
|
2211
|
+
// rejected token isn't one of this form's fields.
|
|
2212
|
+
function _fieldFromValidatorError(e, modulePrefix, formFields) {
|
|
2213
|
+
var raw = (e && e.message) || "";
|
|
2214
|
+
var prefixRe = new RegExp("^" + modulePrefix + "(?:\\.\\w+)?:\\s+");
|
|
2215
|
+
var m = new RegExp("^" + modulePrefix + "(?:\\.\\w+)?:\\s+([a-z_]+)\\b").exec(raw);
|
|
2216
|
+
var field = m && m[1];
|
|
2217
|
+
if (!field || !formFields || !Object.prototype.hasOwnProperty.call(formFields, field)) return null;
|
|
2218
|
+
return { field: field, message: raw.replace(prefixRe, "") };
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
// Build the aria-invalid + aria-describedby attribute fragment for an input
|
|
2222
|
+
// when `inv` (a { field, message } from _fieldFromValidatorError) names this
|
|
2223
|
+
// field. Empty string otherwise. `idPrefix` + field is the static, escaped id.
|
|
2224
|
+
function _fieldAriaAttr(idPrefix, name, inv) {
|
|
2225
|
+
if (!inv || inv.field !== name) return "";
|
|
2226
|
+
return " aria-invalid=\"true\" aria-describedby=\"" + b.template.escapeHtml(idPrefix + name) + "\"";
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// Build the adjacent per-field error span (role="alert") for the rejected
|
|
2230
|
+
// field; empty string otherwise. The reason is escaped operator-validator
|
|
2231
|
+
// prose; the id is the static, escaped `idPrefix + name`.
|
|
2232
|
+
function _fieldErrorSpan(idPrefix, name, inv) {
|
|
2233
|
+
if (!inv || inv.field !== name) return "";
|
|
2234
|
+
return "<span class=\"form-field__error\" id=\"" + b.template.escapeHtml(idPrefix + name) +
|
|
2235
|
+
"\" role=\"alert\">" + b.template.escapeHtml(inv.message == null ? "" : String(inv.message)) + "</span>";
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2195
2238
|
var PRODUCT_PAGE =
|
|
2196
2239
|
"<section class=\"pdp\">\n" +
|
|
2197
2240
|
" <nav class=\"breadcrumb\" aria-label=\"Breadcrumb\">\n" +
|
|
@@ -2486,17 +2529,20 @@ var REVIEW_FORM_PAGE =
|
|
|
2486
2529
|
" <h1 class=\"review-form-page__title\">Review {{title}}</h1>\n" +
|
|
2487
2530
|
" RAW_NOTICE_PLACEHOLDER\n" +
|
|
2488
2531
|
" <form class=\"review-form\" method=\"post\" action=\"/products/{{slug}}/review\">\n" +
|
|
2489
|
-
" <fieldset class=\"review-form__rating\">\n" +
|
|
2532
|
+
" <fieldset class=\"review-form__rating\"RAW_RATING_ARIA_PLACEHOLDER>\n" +
|
|
2490
2533
|
" <legend>Your rating</legend>\n" +
|
|
2491
2534
|
" RAW_STARS_PLACEHOLDER\n" +
|
|
2535
|
+
" RAW_RATING_ERROR_PLACEHOLDER\n" +
|
|
2492
2536
|
" </fieldset>\n" +
|
|
2493
2537
|
" <label class=\"form-field\">\n" +
|
|
2494
2538
|
" <span class=\"form-field__label\">Title</span>\n" +
|
|
2495
|
-
" <input type=\"text\" name=\"title\" maxlength=\"120\" required autocomplete=\"off\">\n" +
|
|
2539
|
+
" <input type=\"text\" name=\"title\" maxlength=\"120\" required autocomplete=\"off\"RAW_TITLE_ARIA_PLACEHOLDER>\n" +
|
|
2540
|
+
" RAW_TITLE_ERROR_PLACEHOLDER\n" +
|
|
2496
2541
|
" </label>\n" +
|
|
2497
2542
|
" <label class=\"form-field\">\n" +
|
|
2498
2543
|
" <span class=\"form-field__label\">Your review</span>\n" +
|
|
2499
|
-
" <textarea name=\"body\" maxlength=\"4000\" rows=\"6\"></textarea>\n" +
|
|
2544
|
+
" <textarea name=\"body\" maxlength=\"4000\" rows=\"6\"RAW_BODY_ARIA_PLACEHOLDER></textarea>\n" +
|
|
2545
|
+
" RAW_BODY_ERROR_PLACEHOLDER\n" +
|
|
2500
2546
|
" </label>\n" +
|
|
2501
2547
|
" <button type=\"submit\" class=\"btn-primary\">Submit review</button>\n" +
|
|
2502
2548
|
" </form>\n" +
|
|
@@ -2519,12 +2565,17 @@ function renderReviewForm(opts) {
|
|
|
2519
2565
|
var notice = opts.notice
|
|
2520
2566
|
? "<p class=\"form-notice form-notice--error\" role=\"alert\">" + esc(String(opts.notice)) + "</p>"
|
|
2521
2567
|
: "";
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2568
|
+
// On a validation re-render, mark the one rejected control. The rating is a
|
|
2569
|
+
// <fieldset> (the aria goes on the group, per the existing fieldset/legend
|
|
2570
|
+
// shape); title/body are ordinary inputs. ids are static "review-err-<name>".
|
|
2571
|
+
var inv = opts.invalid_field || null;
|
|
2572
|
+
var body = _render(REVIEW_FORM_PAGE, { title: opts.product.title, slug: slug })
|
|
2526
2573
|
.replace("RAW_NOTICE_PLACEHOLDER", notice)
|
|
2527
2574
|
.replace("RAW_STARS_PLACEHOLDER", stars);
|
|
2575
|
+
["rating", "title", "body"].forEach(function (name) {
|
|
2576
|
+
body = _spliceRaw(body, "RAW_" + name.toUpperCase() + "_ARIA_PLACEHOLDER", _fieldAriaAttr("review-err-", name, inv));
|
|
2577
|
+
body = _spliceRaw(body, "RAW_" + name.toUpperCase() + "_ERROR_PLACEHOLDER", _fieldErrorSpan("review-err-", name, inv));
|
|
2578
|
+
});
|
|
2528
2579
|
return _wrap({
|
|
2529
2580
|
title: "Review " + opts.product.title,
|
|
2530
2581
|
shop_name: opts.shop_name || "blamejs.shop",
|
|
@@ -2631,7 +2682,8 @@ var QA_FORM_PAGE =
|
|
|
2631
2682
|
" <form class=\"review-form\" method=\"post\" action=\"/products/{{slug}}/question\">\n" +
|
|
2632
2683
|
" <label class=\"form-field\">\n" +
|
|
2633
2684
|
" <span class=\"form-field__label\">Your question</span>\n" +
|
|
2634
|
-
" <textarea name=\"body\" maxlength=\"4000\" rows=\"6\"
|
|
2685
|
+
" <textarea name=\"body\" maxlength=\"4000\" rows=\"6\" requiredRAW_BODY_ARIA_PLACEHOLDER></textarea>\n" +
|
|
2686
|
+
" RAW_BODY_ERROR_PLACEHOLDER\n" +
|
|
2635
2687
|
" </label>\n" +
|
|
2636
2688
|
" <button type=\"submit\" class=\"btn-primary\">Submit question</button>\n" +
|
|
2637
2689
|
" </form>\n" +
|
|
@@ -2645,11 +2697,16 @@ function renderQuestionForm(opts) {
|
|
|
2645
2697
|
var notice = opts.notice
|
|
2646
2698
|
? "<p class=\"form-notice form-notice--error\" role=\"alert\">" + esc(String(opts.notice)) + "</p>"
|
|
2647
2699
|
: "";
|
|
2700
|
+
// Single field (`body`); on a re-render, mark it with aria-invalid + an
|
|
2701
|
+
// adjacent error span. id is the static "qa-err-body".
|
|
2702
|
+
var inv = opts.invalid_field || null;
|
|
2648
2703
|
var body = _render(QA_FORM_PAGE, {
|
|
2649
2704
|
title: opts.product.title,
|
|
2650
2705
|
slug: opts.product.slug,
|
|
2651
2706
|
})
|
|
2652
2707
|
.replace("RAW_NOTICE_PLACEHOLDER", notice);
|
|
2708
|
+
body = _spliceRaw(body, "RAW_BODY_ARIA_PLACEHOLDER", _fieldAriaAttr("qa-err-", "body", inv));
|
|
2709
|
+
body = _spliceRaw(body, "RAW_BODY_ERROR_PLACEHOLDER", _fieldErrorSpan("qa-err-", "body", inv));
|
|
2653
2710
|
return _wrap({
|
|
2654
2711
|
title: "Ask about " + opts.product.title,
|
|
2655
2712
|
shop_name: opts.shop_name || "blamejs.shop",
|
|
@@ -3545,32 +3602,59 @@ function _addrField(name, labelText, value, opts) {
|
|
|
3545
3602
|
// The `*` is a color-only visual cue; pair it with a visually-hidden
|
|
3546
3603
|
// "(required)" so a screen reader announces the field's requiredness.
|
|
3547
3604
|
var req = opts.required ? " <span class=\"form-field__req\" aria-hidden=\"true\">*</span><span class=\"sr-only\">(required)</span>" : "";
|
|
3605
|
+
// On a validation re-render, the rejected field carries aria-invalid +
|
|
3606
|
+
// aria-describedby pointing at an adjacent error span (WCAG 3.3.1/3.3.3).
|
|
3607
|
+
// The id is a static prefix + field name (attacker-uncontrolled) but is
|
|
3608
|
+
// still escaped for defense-in-depth; the reason is operator-validator
|
|
3609
|
+
// prose, escaped via esc().
|
|
3610
|
+
var errSpan = "";
|
|
3611
|
+
if (opts.invalid) {
|
|
3612
|
+
attrs += " aria-invalid=\"true\" aria-describedby=\"" + esc(opts.error_id) + "\"";
|
|
3613
|
+
errSpan = "<span class=\"form-field__error\" id=\"" + esc(opts.error_id) +
|
|
3614
|
+
"\" role=\"alert\">" + esc(opts.error_msg == null ? "" : String(opts.error_msg)) + "</span>";
|
|
3615
|
+
}
|
|
3548
3616
|
return "<label class=\"form-field\">" +
|
|
3549
3617
|
"<span class=\"form-field__label\">" + esc(labelText) + req + "</span>" +
|
|
3550
3618
|
"<input type=\"text\" name=\"" + esc(name) + "\" value=\"" + esc(value == null ? "" : String(value)) + "\"" + attrs + ">" +
|
|
3619
|
+
errSpan +
|
|
3551
3620
|
"</label>";
|
|
3552
3621
|
}
|
|
3553
3622
|
|
|
3554
3623
|
// Shared add/edit address form. `addr` pre-fills for edit (null = add).
|
|
3555
|
-
|
|
3624
|
+
// `invalidField` (optional) is a { field, message } picked off the
|
|
3625
|
+
// backend-thrown validator error so the one rejected input renders with
|
|
3626
|
+
// aria-invalid + a per-field error span (WCAG 3.3.1/3.3.3). When null, every
|
|
3627
|
+
// field renders byte-identically to the no-error path.
|
|
3628
|
+
function _addressForm(action, addr, submitLabel, invalidField) {
|
|
3556
3629
|
var esc = b.template.escapeHtml;
|
|
3557
3630
|
addr = addr || {};
|
|
3558
3631
|
function _checked(v) { return Number(v) === 1 ? " checked" : ""; }
|
|
3632
|
+
// Merge the per-field invalid marker into a field's opts when it is the one
|
|
3633
|
+
// the validator rejected. The id is a static "addr-err-<name>" so it is
|
|
3634
|
+
// attacker-uncontrolled.
|
|
3635
|
+
function _mark(name, opts) {
|
|
3636
|
+
if (invalidField && invalidField.field === name) {
|
|
3637
|
+
opts.invalid = true;
|
|
3638
|
+
opts.error_id = "addr-err-" + name;
|
|
3639
|
+
opts.error_msg = invalidField.message;
|
|
3640
|
+
}
|
|
3641
|
+
return opts;
|
|
3642
|
+
}
|
|
3559
3643
|
return "<form class=\"address-form form-stack\" method=\"post\" action=\"" + esc(action) + "\">" +
|
|
3560
|
-
_addrField("recipient_name", "Recipient name", addr.recipient_name, { required: true, maxlength: 120, autocomplete: "name" }) +
|
|
3561
|
-
_addrField("label", "Label (e.g. Home, Work)", addr.label, { maxlength: 60 }) +
|
|
3562
|
-
_addrField("company", "Company", addr.company, { maxlength: 120, autocomplete: "organization" }) +
|
|
3563
|
-
_addrField("street_line1", "Street address", addr.street_line1, { required: true, maxlength: 200, autocomplete: "address-line1" }) +
|
|
3564
|
-
_addrField("street_line2", "Apt / suite / unit", addr.street_line2, { maxlength: 200, autocomplete: "address-line2" }) +
|
|
3644
|
+
_addrField("recipient_name", "Recipient name", addr.recipient_name, _mark("recipient_name", { required: true, maxlength: 120, autocomplete: "name" })) +
|
|
3645
|
+
_addrField("label", "Label (e.g. Home, Work)", addr.label, _mark("label", { maxlength: 60 })) +
|
|
3646
|
+
_addrField("company", "Company", addr.company, _mark("company", { maxlength: 120, autocomplete: "organization" })) +
|
|
3647
|
+
_addrField("street_line1", "Street address", addr.street_line1, _mark("street_line1", { required: true, maxlength: 200, autocomplete: "address-line1" })) +
|
|
3648
|
+
_addrField("street_line2", "Apt / suite / unit", addr.street_line2, _mark("street_line2", { maxlength: 200, autocomplete: "address-line2" })) +
|
|
3565
3649
|
"<div class=\"form-row form-row--inline\">" +
|
|
3566
|
-
_addrField("city", "City", addr.city, { required: true, maxlength: 120, autocomplete: "address-level2" }) +
|
|
3567
|
-
_addrField("region", "State / region", addr.region, { maxlength: 120, autocomplete: "address-level1" }) +
|
|
3650
|
+
_addrField("city", "City", addr.city, _mark("city", { required: true, maxlength: 120, autocomplete: "address-level2" })) +
|
|
3651
|
+
_addrField("region", "State / region", addr.region, _mark("region", { maxlength: 120, autocomplete: "address-level1" })) +
|
|
3568
3652
|
"</div>" +
|
|
3569
3653
|
"<div class=\"form-row form-row--inline\">" +
|
|
3570
|
-
_addrField("postal_code", "Postal code", addr.postal_code, { required: true, maxlength: 32, autocomplete: "postal-code" }) +
|
|
3571
|
-
_addrField("country", "Country (ISO 3166-1)", addr.country || "US", { required: true, maxlength: 2, pattern: "[A-Za-z]{2}", autocomplete: "country" }) +
|
|
3654
|
+
_addrField("postal_code", "Postal code", addr.postal_code, _mark("postal_code", { required: true, maxlength: 32, autocomplete: "postal-code" })) +
|
|
3655
|
+
_addrField("country", "Country (ISO 3166-1)", addr.country || "US", _mark("country", { required: true, maxlength: 2, pattern: "[A-Za-z]{2}", autocomplete: "country" })) +
|
|
3572
3656
|
"</div>" +
|
|
3573
|
-
_addrField("phone", "Phone", addr.phone, { maxlength: 40, autocomplete: "tel" }) +
|
|
3657
|
+
_addrField("phone", "Phone", addr.phone, _mark("phone", { maxlength: 40, autocomplete: "tel" })) +
|
|
3574
3658
|
"<label class=\"address-form__check\"><input type=\"checkbox\" name=\"is_default_shipping\" value=\"1\"" + _checked(addr.is_default_shipping) + "> Default shipping address</label>" +
|
|
3575
3659
|
"<label class=\"address-form__check\"><input type=\"checkbox\" name=\"is_default_billing\" value=\"1\"" + _checked(addr.is_default_billing) + "> Default billing address</label>" +
|
|
3576
3660
|
"<button type=\"submit\" class=\"btn-primary\">" + esc(submitLabel) + "</button>" +
|
|
@@ -3642,7 +3726,7 @@ function renderAddresses(opts) {
|
|
|
3642
3726
|
listHtml +
|
|
3643
3727
|
"<h2 class=\"account-addresses__form-title\">" + esc(formHeading) + "</h2>" +
|
|
3644
3728
|
notice +
|
|
3645
|
-
_addressForm(formAction, editing, editing ? "Save changes" : "Add address") +
|
|
3729
|
+
_addressForm(formAction, editing, editing ? "Save changes" : "Add address", opts.invalid_field || null) +
|
|
3646
3730
|
"</section>";
|
|
3647
3731
|
return _wrap({
|
|
3648
3732
|
title: "Addresses",
|
|
@@ -4615,6 +4699,13 @@ function renderSupportNew(opts) {
|
|
|
4615
4699
|
var notice = opts.notice
|
|
4616
4700
|
? "<p class=\"form-notice form-notice--error\" role=\"alert\">" + esc(String(opts.notice)) + "</p>"
|
|
4617
4701
|
: "";
|
|
4702
|
+
// On a validation re-render, mark the one rejected field with aria-invalid +
|
|
4703
|
+
// an adjacent error span (WCAG 3.3.1/3.3.3) via the shared field helpers.
|
|
4704
|
+
// `opts.invalid_field` is the { field, message } the route picked off the
|
|
4705
|
+
// backend-thrown error; ids are static "support-err-<name>".
|
|
4706
|
+
var inv = opts.invalid_field || null;
|
|
4707
|
+
function _supAria(name) { return _fieldAriaAttr("support-err-", name, inv); }
|
|
4708
|
+
function _supErr(name) { return _fieldErrorSpan("support-err-", name, inv); }
|
|
4618
4709
|
var body =
|
|
4619
4710
|
"<section class=\"return-form-page\">" +
|
|
4620
4711
|
"<nav class=\"breadcrumb\" aria-label=\"Breadcrumb\"><ol>" +
|
|
@@ -4626,14 +4717,14 @@ function renderSupportNew(opts) {
|
|
|
4626
4717
|
notice +
|
|
4627
4718
|
"<form class=\"return-form form-stack\" method=\"post\" action=\"/account/support\">" +
|
|
4628
4719
|
"<label class=\"form-field\"><span class=\"form-field__label\">Email for our reply</span>" +
|
|
4629
|
-
"<input type=\"email\" name=\"customer_email\" value=\"" + esc(v.customer_email == null ? "" : String(v.customer_email)) + "\" required autocomplete=\"email\" maxlength=\"254\"
|
|
4720
|
+
"<input type=\"email\" name=\"customer_email\" value=\"" + esc(v.customer_email == null ? "" : String(v.customer_email)) + "\" required autocomplete=\"email\" maxlength=\"254\"" + _supAria("customer_email") + ">" + _supErr("customer_email") + "</label>" +
|
|
4630
4721
|
"<label class=\"form-field\"><span class=\"form-field__label\">Category</span>" +
|
|
4631
|
-
"<select name=\"category\" required>" + categoryOpts + "</select
|
|
4722
|
+
"<select name=\"category\" required" + _supAria("category") + ">" + categoryOpts + "</select>" + _supErr("category") + "</label>" +
|
|
4632
4723
|
orderField +
|
|
4633
4724
|
"<label class=\"form-field\"><span class=\"form-field__label\">Subject</span>" +
|
|
4634
|
-
"<input type=\"text\" name=\"subject\" value=\"" + esc(v.subject == null ? "" : String(v.subject)) + "\" required maxlength=\"200\"
|
|
4725
|
+
"<input type=\"text\" name=\"subject\" value=\"" + esc(v.subject == null ? "" : String(v.subject)) + "\" required maxlength=\"200\"" + _supAria("subject") + ">" + _supErr("subject") + "</label>" +
|
|
4635
4726
|
"<label class=\"form-field\"><span class=\"form-field__label\">How can we help?</span>" +
|
|
4636
|
-
"<textarea name=\"body\" required maxlength=\"8000\" rows=\"6\">" + esc(v.body == null ? "" : String(v.body)) + "</textarea
|
|
4727
|
+
"<textarea name=\"body\" required maxlength=\"8000\" rows=\"6\"" + _supAria("body") + ">" + esc(v.body == null ? "" : String(v.body)) + "</textarea>" + _supErr("body") + "</label>" +
|
|
4637
4728
|
"<button type=\"submit\" class=\"btn-primary\">Send</button>" +
|
|
4638
4729
|
"</form>" +
|
|
4639
4730
|
"</section>";
|
|
@@ -14567,7 +14658,7 @@ function mount(router, deps) {
|
|
|
14567
14658
|
if (kind === "default-billing") return "Default billing address updated.";
|
|
14568
14659
|
return null;
|
|
14569
14660
|
}
|
|
14570
|
-
async function _renderAddrPage(req, res, auth, editAddr, notice, code) {
|
|
14661
|
+
async function _renderAddrPage(req, res, auth, editAddr, notice, code, invalidField) {
|
|
14571
14662
|
var rows = await deps.addresses.listForCustomer(auth.customer_id, { limit: 50 });
|
|
14572
14663
|
var cartCount = await _cartCountForReq(req);
|
|
14573
14664
|
var url = req.url ? new URL(req.url, "http://localhost") : null;
|
|
@@ -14577,15 +14668,24 @@ function mount(router, deps) {
|
|
|
14577
14668
|
// when the ?undo=<id> marker round-trips a real owned address id.
|
|
14578
14669
|
var undoId = (okKind === "removed" && url) ? url.searchParams.get("undo") : null;
|
|
14579
14670
|
_send(res, code || 200, renderAddresses({
|
|
14580
|
-
addresses:
|
|
14581
|
-
edit:
|
|
14582
|
-
notice:
|
|
14583
|
-
success:
|
|
14584
|
-
undo_id:
|
|
14585
|
-
|
|
14586
|
-
|
|
14671
|
+
addresses: rows,
|
|
14672
|
+
edit: editAddr || null,
|
|
14673
|
+
notice: notice || null,
|
|
14674
|
+
success: success,
|
|
14675
|
+
undo_id: undoId || null,
|
|
14676
|
+
invalid_field: invalidField || null,
|
|
14677
|
+
shop_name: shopName,
|
|
14678
|
+
cart_count: cartCount,
|
|
14587
14679
|
}));
|
|
14588
14680
|
}
|
|
14681
|
+
// The address form's renderable fields, by `name`. An error on a
|
|
14682
|
+
// non-form internal (customer_id, address_id) returns null from the
|
|
14683
|
+
// shared extractor, so the page-top banner still shows but no input is
|
|
14684
|
+
// falsely marked.
|
|
14685
|
+
var _ADDR_FORM_FIELDS = {
|
|
14686
|
+
recipient_name: 1, label: 1, company: 1, street_line1: 1, street_line2: 1,
|
|
14687
|
+
city: 1, region: 1, postal_code: 1, country: 1, phone: 1,
|
|
14688
|
+
};
|
|
14589
14689
|
function _addrInput(body, customerId) {
|
|
14590
14690
|
return {
|
|
14591
14691
|
customer_id: customerId,
|
|
@@ -14622,7 +14722,10 @@ function mount(router, deps) {
|
|
|
14622
14722
|
try {
|
|
14623
14723
|
await deps.addresses.add(_addrInput(req.body || {}, auth.customer_id));
|
|
14624
14724
|
} catch (e) {
|
|
14625
|
-
if (e instanceof TypeError)
|
|
14725
|
+
if (e instanceof TypeError) {
|
|
14726
|
+
var inv = _fieldFromValidatorError(e, "addresses", _ADDR_FORM_FIELDS);
|
|
14727
|
+
return _renderAddrPage(req, res, auth, null, (e && e.message) || "Please check the address.", 400, inv);
|
|
14728
|
+
}
|
|
14626
14729
|
throw e;
|
|
14627
14730
|
}
|
|
14628
14731
|
res.status(303); res.setHeader && res.setHeader("location", "/account/addresses?ok=added");
|
|
@@ -14637,7 +14740,8 @@ function mount(router, deps) {
|
|
|
14637
14740
|
} catch (e) {
|
|
14638
14741
|
if (e instanceof TypeError) {
|
|
14639
14742
|
var merged = Object.assign({}, addr, _addrInput(req.body || {}, auth.customer_id));
|
|
14640
|
-
|
|
14743
|
+
var inv = _fieldFromValidatorError(e, "addresses", _ADDR_FORM_FIELDS);
|
|
14744
|
+
return _renderAddrPage(req, res, auth, merged, (e && e.message) || "Please check the address.", 400, inv);
|
|
14641
14745
|
}
|
|
14642
14746
|
throw e;
|
|
14643
14747
|
}
|
|
@@ -15730,11 +15834,12 @@ function mount(router, deps) {
|
|
|
15730
15834
|
} catch (e) {
|
|
15731
15835
|
if (e instanceof TypeError) {
|
|
15732
15836
|
return _send(res, 400, renderSupportNew({
|
|
15733
|
-
orders:
|
|
15734
|
-
values:
|
|
15735
|
-
notice:
|
|
15736
|
-
|
|
15737
|
-
|
|
15837
|
+
orders: await _orders(),
|
|
15838
|
+
values: body,
|
|
15839
|
+
notice: (e && e.message || "").replace(/^supportTickets[.:]\s*/, "") || "Please check your ticket and try again.",
|
|
15840
|
+
invalid_field: _fieldFromValidatorError(e, "supportTickets", { customer_email: 1, category: 1, subject: 1, body: 1 }),
|
|
15841
|
+
shop_name: shopName,
|
|
15842
|
+
cart_count: cartCount,
|
|
15738
15843
|
}));
|
|
15739
15844
|
}
|
|
15740
15845
|
throw e;
|
|
@@ -16418,10 +16523,11 @@ function mount(router, deps) {
|
|
|
16418
16523
|
// anything else is a real 500.
|
|
16419
16524
|
if (e instanceof TypeError) {
|
|
16420
16525
|
return _send(res, 400, renderReviewForm({
|
|
16421
|
-
product:
|
|
16422
|
-
notice:
|
|
16423
|
-
|
|
16424
|
-
|
|
16526
|
+
product: { title: ctx.product.title, slug: ctx.product.slug },
|
|
16527
|
+
notice: (e && e.message) || "Please check your review and try again.",
|
|
16528
|
+
invalid_field: _fieldFromValidatorError(e, "reviews", { rating: 1, title: 1, body: 1 }),
|
|
16529
|
+
shop_name: shopName,
|
|
16530
|
+
cart_count: ctx.cartCount,
|
|
16425
16531
|
}));
|
|
16426
16532
|
}
|
|
16427
16533
|
throw e;
|
|
@@ -16489,10 +16595,11 @@ function mount(router, deps) {
|
|
|
16489
16595
|
// anything else is a real 500.
|
|
16490
16596
|
if (e instanceof TypeError) {
|
|
16491
16597
|
return _send(res, 400, renderQuestionForm({
|
|
16492
|
-
product:
|
|
16493
|
-
notice:
|
|
16494
|
-
|
|
16495
|
-
|
|
16598
|
+
product: { title: ctx.product.title, slug: ctx.product.slug },
|
|
16599
|
+
notice: (e && e.message) || "Please check your question and try again.",
|
|
16600
|
+
invalid_field: _fieldFromValidatorError(e, "productQA", { body: 1 }),
|
|
16601
|
+
shop_name: shopName,
|
|
16602
|
+
cart_count: ctx.cartCount,
|
|
16496
16603
|
}));
|
|
16497
16604
|
}
|
|
16498
16605
|
throw e;
|
package/package.json
CHANGED