added on local at 2026-07-01 13:48:09
| 1 | <!-- Shipping settings. Backed by /shipping_settings.cgi. --> | |
| 2 | ||
| 3 | <style> | |
| 4 | .ss-tabs { display:flex; gap:4px; border-bottom:1px solid var(--col-border); margin-bottom:18px; } | |
| 5 | .ss-tab { padding:10px 16px; color:var(--col-text-3); font-size:12px; font-weight:700; letter-spacing:1px; text-transform:uppercase; text-decoration:none; border-bottom:2px solid transparent; cursor:pointer; background:none; border-top:none; border-left:none; border-right:none; font-family:inherit; } | |
| 6 | .ss-tab:hover { color:var(--col-text); } | |
| 7 | .ss-tab.is-active { color:var(--col-accent-bright); border-bottom-color:var(--col-accent); } | |
| 8 | .ss-pane { display:none; } | |
| 9 | .ss-pane.is-active { display:block; } | |
| 10 | ||
| 11 | .ss-card { background:var(--col-surface-1); border:1px solid var(--col-border); border-radius:12px; padding:20px; margin-bottom:14px; } | |
| 12 | .ss-card h3 { font-family:var(--font-display,inherit); font-size:15px; text-transform:uppercase; letter-spacing:1.5px; color:var(--col-text); margin:0 0 16px; font-weight:700; } | |
| 13 | .ss-grid-2 { display:grid; grid-template-columns:1fr 1fr; gap:14px; } | |
| 14 | .ss-grid-3 { display:grid; grid-template-columns:1fr 1fr 1fr; gap:14px; } | |
| 15 | .ss-grid-4 { display:grid; grid-template-columns:repeat(4, 1fr); gap:14px; } | |
| 16 | \@media (max-width: 800px) { .ss-grid-2, .ss-grid-3, .ss-grid-4 { grid-template-columns:1fr; } } | |
| 17 | ||
| 18 | .ss-carrier-card { border:1px solid var(--col-border); border-radius:10px; padding:14px 18px; margin-bottom:10px; background:var(--col-surface-2); transition:border-color .15s; } | |
| 19 | .ss-carrier-card.is-on { border-color:rgba(34,197,94,0.5); } | |
| 20 | .ss-carrier-head { display:flex; justify-content:space-between; align-items:center; } | |
| 21 | .ss-carrier-name { font-weight:700; font-size:15px; color:var(--col-text); } | |
| 22 | .ss-carrier-toggle { display:inline-flex; gap:6px; align-items:center; font-size:12px; color:var(--col-text-2); cursor:pointer; } | |
| 23 | .ss-carrier-body { display:none; margin-top:14px; } | |
| 24 | .ss-carrier-card.is-on .ss-carrier-body { display:block; } | |
| 25 | ||
| 26 | .ss-zones-table { width:100%; border-collapse:separate; border-spacing:0; font-size:13px; } | |
| 27 | .ss-zones-table th { font-size:11px; text-transform:uppercase; letter-spacing:1px; color:var(--col-text-3); font-weight:700; text-align:left; padding:8px 10px; border-bottom:1px solid var(--col-border); } | |
| 28 | .ss-zones-table td { padding:8px 10px; border-bottom:1px solid var(--col-border); vertical-align:middle; } | |
| 29 | .ss-zones-table input.input, .ss-zones-table select.select { height:32px; font-size:12px; padding:4px 8px; } | |
| 30 | </style> | |
| 31 | ||
| 32 | <div class="page-head"> | |
| 33 | <div> | |
| 34 | <span class="page-eyebrow"><span class="dot"></span> Studio · Shipping</span> | |
| 35 | <h1 class="page-title">Shipping setup</h1> | |
| 36 | <p class="page-subtitle">Origin address, carriers, and zone-based rates. Used by the rate estimator on every physical order.</p> | |
| 37 | </div> | |
| 38 | </div> | |
| 39 | ||
| 40 | [if:$has_flash] | |
| 41 | <div class="banner $flash_kind mb-3"> | |
| 42 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/></svg> | |
| 43 | <div class="text-sm fw-600">$flash_msg</div> | |
| 44 | </div> | |
| 45 | [/if] | |
| 46 | ||
| 47 | <div class="ss-tabs"> | |
| 48 | <button type="button" class="ss-tab is-active" data-tab="origin">Origin & defaults</button> | |
| 49 | <button type="button" class="ss-tab" data-tab="carriers">Carriers</button> | |
| 50 | <button type="button" class="ss-tab" data-tab="zones">Zones & rates</button> | |
| 51 | </div> | |
| 52 | ||
| 53 | <!-- ============ ORIGIN ============ --> | |
| 54 | <div class="ss-pane is-active" data-pane="origin"> | |
| 55 | <form method="POST" action="/shipping_settings.cgi"> | |
| 56 | <input type="hidden" name="_act" value="save_origin"> | |
| 57 | ||
| 58 | <div class="ss-card"> | |
| 59 | <h3>Return / ship-from address</h3> | |
| 60 | <p class="text-xs text-dim" style="margin:0 0 14px;line-height:1.5">Carriers print this on every label so undeliverable packages come back to you. It also appears on the buyer's packing slip.</p> | |
| 61 | ||
| 62 | <div class="ss-grid-2"> | |
| 63 | <div class="form-group"> | |
| 64 | <label class="form-label">Contact name</label> | |
| 65 | <input type="text" name="origin_name" class="input" value="$origin_name" placeholder="Full name on the package"> | |
| 66 | </div> | |
| 67 | <div class="form-group"> | |
| 68 | <label class="form-label">Company <span class="text-xs text-dim">(optional)</span></label> | |
| 69 | <input type="text" name="origin_company" class="input" value="$origin_company"> | |
| 70 | </div> | |
| 71 | </div> | |
| 72 | ||
| 73 | <div class="form-group"> | |
| 74 | <label class="form-label">Street address line 1</label> | |
| 75 | <input type="text" name="origin_line1" class="input" value="$origin_line1" placeholder="Street + number"> | |
| 76 | </div> | |
| 77 | <div class="form-group"> | |
| 78 | <label class="form-label">Street address line 2 <span class="text-xs text-dim">(optional)</span></label> | |
| 79 | <input type="text" name="origin_line2" class="input" value="$origin_line2" placeholder="Apt / suite / floor"> | |
| 80 | </div> | |
| 81 | ||
| 82 | <div class="ss-grid-3"> | |
| 83 | <div class="form-group"> | |
| 84 | <label class="form-label">City</label> | |
| 85 | <input type="text" name="origin_city" class="input" value="$origin_city"> | |
| 86 | </div> | |
| 87 | <div class="form-group"> | |
| 88 | <label class="form-label">State / province</label> | |
| 89 | <input type="text" name="origin_region" class="input" value="$origin_region"> | |
| 90 | </div> | |
| 91 | <div class="form-group"> | |
| 92 | <label class="form-label">Postal code</label> | |
| 93 | <input type="text" name="origin_postal" class="input" value="$origin_postal"> | |
| 94 | </div> | |
| 95 | </div> | |
| 96 | ||
| 97 | <div class="ss-grid-3"> | |
| 98 | <div class="form-group"> | |
| 99 | <label class="form-label">Country <span class="text-xs text-dim">(ISO 2-letter code)</span></label> | |
| 100 | <input type="text" name="origin_country" class="input" value="$origin_country" maxlength="2" style="text-transform:uppercase"> | |
| 101 | </div> | |
| 102 | <div class="form-group"> | |
| 103 | <label class="form-label">Phone</label> | |
| 104 | <input type="text" name="origin_phone" class="input" value="$origin_phone"> | |
| 105 | </div> | |
| 106 | <div class="form-group"> | |
| 107 | <label class="form-label">Email</label> | |
| 108 | <input type="text" name="origin_email" class="input" value="$origin_email"> | |
| 109 | </div> | |
| 110 | </div> | |
| 111 | </div> | |
| 112 | ||
| 113 | <div class="ss-card"> | |
| 114 | <h3>Defaults</h3> | |
| 115 | <div class="ss-grid-2"> | |
| 116 | <div class="form-group"> | |
| 117 | <label class="form-label">Default carrier</label> | |
| 118 | <select name="default_carrier" class="select" id="ssDefaultCarrier"> | |
| 119 | <option value="manual">Manual / paper label</option> | |
| 120 | <option value="usps">USPS</option> | |
| 121 | <option value="ups">UPS</option> | |
| 122 | <option value="fedex">FedEx</option> | |
| 123 | <option value="dhl">DHL</option> | |
| 124 | <option value="other">Other</option> | |
| 125 | </select> | |
| 126 | </div> | |
| 127 | <div class="form-group"> | |
| 128 | <label class="form-label">Default service <span class="text-xs text-dim">(carrier service name)</span></label> | |
| 129 | <input type="text" name="default_service" class="input" value="$default_service" placeholder="e.g. Priority Mail"> | |
| 130 | </div> | |
| 131 | </div> | |
| 132 | ||
| 133 | <div class="ss-grid-4"> | |
| 134 | <div class="form-group"> | |
| 135 | <label class="form-label">Handling fee (cents)</label> | |
| 136 | <input type="text" name="handling_fee_cents" class="input" value="$handling_fee_cents"> | |
| 137 | </div> | |
| 138 | <div class="form-group"> | |
| 139 | <label class="form-label">Handling time (days)</label> | |
| 140 | <input type="text" name="default_handling_days" class="input" value="$default_handling_days"> | |
| 141 | </div> | |
| 142 | <div class="form-group"> | |
| 143 | <label class="form-label">Default insurance (cents) <span class="text-xs text-dim">(optional)</span></label> | |
| 144 | <input type="text" name="insurance_default_cents" class="input" value="$insurance_default_cents"> | |
| 145 | </div> | |
| 146 | <div class="form-group" style="display:flex;flex-direction:column;justify-content:flex-end;gap:6px"> | |
| 147 | <label style="display:flex;gap:6px;align-items:center;font-size:13px;color:var(--col-text-2);cursor:pointer"> | |
| 148 | <input type="checkbox" name="auto_email_tracking" value="1" $auto_email_tracking_chk> Email tracking to buyer automatically | |
| 149 | </label> | |
| 150 | <label style="display:flex;gap:6px;align-items:center;font-size:13px;color:var(--col-text-2);cursor:pointer"> | |
| 151 | <input type="checkbox" name="require_signature" value="1" $require_signature_chk> Require signature by default | |
| 152 | </label> | |
| 153 | </div> | |
| 154 | </div> | |
| 155 | </div> | |
| 156 | ||
| 157 | <div style="display:flex;justify-content:flex-end;gap:8px;margin-bottom:18px"> | |
| 158 | <button type="submit" class="btn btn-primary">Save origin & defaults</button> | |
| 159 | </div> | |
| 160 | </form> | |
| 161 | ||
| 162 | <script> | |
| 163 | // Pre-select the saved default carrier | |
| 164 | document.getElementById('ssDefaultCarrier').value = '$default_carrier'; | |
| 165 | </script> | |
| 166 | </div> | |
| 167 | ||
| 168 | <!-- ============ CARRIERS ============ --> | |
| 169 | <div class="ss-pane" data-pane="carriers"> | |
| 170 | <div class="ss-card" style="background:rgba(96,165,250,0.06);border-color:rgba(96,165,250,0.20)"> | |
| 171 | <p class="text-sm" style="margin:0;color:var(--col-text);line-height:1.5"> | |
| 172 | <strong>How this works:</strong> turn on carriers you ship with so they appear in the dropdown when you create a label. Adding API credentials here is optional — until live carrier API integration is wired up, labels are recorded manually (you paste the tracking number from the carrier's site). Saved credentials wait for the API integration. | |
| 173 | </p> | |
| 174 | </div> | |
| 175 | ||
| 176 | [loop:@carriers] | |
| 177 | <form method="POST" action="/shipping_settings.cgi"> | |
| 178 | <input type="hidden" name="_act" value="save_carrier"> | |
| 179 | <input type="hidden" name="carrier" value="$loop1.slug"> | |
| 180 | <div class="ss-carrier-card" id="ssCarrierCard_$loop1.slug"> | |
| 181 | <div class="ss-carrier-head"> | |
| 182 | <div> | |
| 183 | <div class="ss-carrier-name">$loop1.name <span class="text-xs text-dim" style="font-weight:400;letter-spacing:0">· $loop1.full</span></div> | |
| 184 | [if:$loop1.is_configured]<div class="text-xs text-dim" style="margin-top:2px">Saved.</div>[/if] | |
| 185 | </div> | |
| 186 | <label class="ss-carrier-toggle"> | |
| 187 | <input type="checkbox" name="is_enabled" value="1" $loop1.is_enabled class="ss-carrier-enable"> Enabled | |
| 188 | </label> | |
| 189 | </div> | |
| 190 | ||
| 191 | <div class="ss-carrier-body"> | |
| 192 | <p class="text-xs text-dim" style="margin:0 0 12px;line-height:1.5">$loop1.connect_help</p> | |
| 193 | <div class="ss-grid-2"> | |
| 194 | <div class="form-group"> | |
| 195 | <label class="form-label">Display name</label> | |
| 196 | <input type="text" name="display_name" class="input" value="$loop1.display_name" placeholder="e.g. USPS Priority Mail"> | |
| 197 | </div> | |
| 198 | <div class="form-group"> | |
| 199 | <label class="form-label">Default service</label> | |
| 200 | <input type="text" name="default_service" class="input" value="$loop1.default_service" placeholder="$loop1.services_list"> | |
| 201 | </div> | |
| 202 | </div> | |
| 203 | <div class="ss-grid-3"> | |
| 204 | <div class="form-group"> | |
| 205 | <label class="form-label">Account number</label> | |
| 206 | <input type="text" name="account_number" class="input" value="$loop1.account_number"> | |
| 207 | </div> | |
| 208 | <div class="form-group"> | |
| 209 | <label class="form-label">API key</label> | |
| 210 | <input type="text" name="api_key" class="input" value="$loop1.api_key"> | |
| 211 | </div> | |
| 212 | <div class="form-group"> | |
| 213 | <label class="form-label">API secret</label> | |
| 214 | <input type="password" name="api_secret" class="input" placeholder="[if:$loop1.api_secret_set](saved -- leave blank to keep)[/if][if:!$loop1.api_secret_set]API secret[/if]"> | |
| 215 | </div> | |
| 216 | </div> | |
| 217 | <div class="form-group"> | |
| 218 | <label class="up-check" style="display:flex;gap:6px;align-items:center;font-size:13px;color:var(--col-text-2);cursor:pointer"> | |
| 219 | <input type="checkbox" name="is_live" value="1" $loop1.is_live> | |
| 220 | <span>Live mode <span class="text-xs text-dim">(unchecked = test/sandbox API)</span></span> | |
| 221 | </label> | |
| 222 | </div> | |
| 223 | <div class="form-group"> | |
| 224 | <label class="form-label">Internal notes</label> | |
| 225 | <input type="text" name="notes" class="input" value="$loop1.notes" placeholder="(visible only to you)"> | |
| 226 | </div> | |
| 227 | <div style="display:flex;justify-content:flex-end"> | |
| 228 | <button type="submit" class="btn btn-primary">Save $loop1.name</button> | |
| 229 | </div> | |
| 230 | </div> | |
| 231 | </div> | |
| 232 | </form> | |
| 233 | [/loop] | |
| 234 | </div> | |
| 235 | ||
| 236 | <!-- ============ ZONES ============ --> | |
| 237 | <div class="ss-pane" data-pane="zones"> | |
| 238 | <div class="ss-card" style="background:rgba(167,139,250,0.06);border-color:rgba(167,139,250,0.20)"> | |
| 239 | <p class="text-sm" style="margin:0;color:var(--col-text);line-height:1.5"> | |
| 240 | <strong>How zones work:</strong> the rate estimator walks zones in <em>sort order</em>, picks the first whose country list matches the buyer's country, and quotes <code>base + per_kg * weight</code> (rounded to whole cents). Leave the country list empty for a catch-all (always last). Set <code>free_over_cents</code> to give buyers free shipping above a subtotal threshold. | |
| 241 | </p> | |
| 242 | </div> | |
| 243 | ||
| 244 | <div class="ss-card"> | |
| 245 | <h3>Existing zones</h3> | |
| 246 | [if:$has_zones] | |
| 247 | <table class="ss-zones-table"> | |
| 248 | <thead> | |
| 249 | <tr> | |
| 250 | <th>Name</th> | |
| 251 | <th>Countries</th> | |
| 252 | <th>Base \$</th> | |
| 253 | <th>Per kg \$</th> | |
| 254 | <th>Free over \$</th> | |
| 255 | <th>Days</th> | |
| 256 | <th>Carrier hint</th> | |
| 257 | <th>Sort</th> | |
| 258 | <th></th> | |
| 259 | </tr> | |
| 260 | </thead> | |
| 261 | <tbody> | |
| 262 | [loop:@zones] | |
| 263 | <tr> | |
| 264 | <td colspan="9" style="padding:0"> | |
| 265 | <form method="POST" action="/shipping_settings.cgi" style="display:contents"> | |
| 266 | <input type="hidden" name="_act" value="zone_update"> | |
| 267 | <input type="hidden" name="id" value="$loop1.id"> | |
| 268 | <table style="width:100%;border-collapse:collapse"><tr> | |
| 269 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);width:140px"><input type="text" name="name" class="input" value="$loop1.name"></td> | |
| 270 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);width:180px"><input type="text" name="country_codes" class="input" value="$loop1.country_codes" placeholder="US,CA"></td> | |
| 271 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);width:90px"><input type="text" name="base_cents" class="input" value="$loop1.base" data-cents="1"></td> | |
| 272 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);width:90px"><input type="text" name="per_kg_cents" class="input" value="$loop1.per_kg" data-cents="1"></td> | |
| 273 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);width:100px"><input type="text" name="free_over_cents" class="input" value="$loop1.free_over" data-cents="1"></td> | |
| 274 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);width:110px"> | |
| 275 | <div style="display:flex;gap:4px"> | |
| 276 | <input type="text" name="delivery_days_min" class="input" value="$loop1.delivery_days_min" placeholder="min" style="width:48px"> | |
| 277 | <input type="text" name="delivery_days_max" class="input" value="$loop1.delivery_days_max" placeholder="max" style="width:48px"> | |
| 278 | </div> | |
| 279 | </td> | |
| 280 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);width:110px"><input type="text" name="carrier_hint" class="input" value="$loop1.carrier_hint" placeholder="usps"></td> | |
| 281 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);width:70px"><input type="text" name="sort_order" class="input" value="$loop1.sort_order" style="width:60px"></td> | |
| 282 | <td style="padding:8px 10px;border-bottom:1px solid var(--col-border);white-space:nowrap;text-align:right"> | |
| 283 | <label style="display:inline-flex;gap:4px;align-items:center;font-size:11px;color:var(--col-text-2);margin-right:6px"> | |
| 284 | <input type="checkbox" name="is_enabled" value="1" $loop1.is_enabled_chk> on | |
| 285 | </label> | |
| 286 | <button type="submit" class="btn btn-primary btn-sm">Save</button> | |
| 287 | </td> | |
| 288 | </tr></table> | |
| 289 | </form> | |
| 290 | <form method="POST" action="/shipping_settings.cgi" style="display:inline" | |
| 291 | data-confirm-title="Delete this zone?" | |
| 292 | data-confirm-message="Quotes for destinations only matched by this zone will fall through to the next matching zone." | |
| 293 | data-confirm-style="danger"> | |
| 294 | <input type="hidden" name="_act" value="zone_delete"> | |
| 295 | <input type="hidden" name="id" value="$loop1.id"> | |
| 296 | <button type="submit" class="btn btn-danger btn-sm" style="margin:8px 10px;float:right">Delete zone</button> | |
| 297 | </form> | |
| 298 | </td> | |
| 299 | </tr> | |
| 300 | [/loop] | |
| 301 | </tbody> | |
| 302 | </table> | |
| 303 | [/if] | |
| 304 | [if:!$has_zones] | |
| 305 | <p class="text-xs text-dim">No zones yet. Add one below.</p> | |
| 306 | [/if] | |
| 307 | </div> | |
| 308 | ||
| 309 | <div class="ss-card"> | |
| 310 | <h3>Add a new zone</h3> | |
| 311 | <p class="text-xs text-dim" style="margin:0 0 12px;line-height:1.5">Prices below are <strong>in cents</strong> (no dollar sign). e.g. \$9.50 = <code>950</code>. Country codes are comma-separated 2-letter ISO codes; leave empty for a catch-all zone.</p> | |
| 312 | <form method="POST" action="/shipping_settings.cgi"> | |
| 313 | <input type="hidden" name="_act" value="zone_create"> | |
| 314 | <div class="ss-grid-2"> | |
| 315 | <div class="form-group"> | |
| 316 | <label class="form-label">Zone name</label> | |
| 317 | <input type="text" name="name" class="input" placeholder="Domestic US" required> | |
| 318 | </div> | |
| 319 | <div class="form-group"> | |
| 320 | <label class="form-label">Countries <span class="text-xs text-dim">(empty = catch-all)</span></label> | |
| 321 | <input type="text" name="country_codes" class="input" placeholder="US,CA,MX"> | |
| 322 | </div> | |
| 323 | </div> | |
| 324 | <div class="ss-grid-4"> | |
| 325 | <div class="form-group"> | |
| 326 | <label class="form-label">Base (cents)</label> | |
| 327 | <input type="text" name="base_cents" class="input" placeholder="500" required> | |
| 328 | </div> | |
| 329 | <div class="form-group"> | |
| 330 | <label class="form-label">Per kg (cents)</label> | |
| 331 | <input type="text" name="per_kg_cents" class="input" placeholder="250"> | |
| 332 | </div> | |
| 333 | <div class="form-group"> | |
| 334 | <label class="form-label">Free over (cents)</label> | |
| 335 | <input type="text" name="free_over_cents" class="input" placeholder="(optional)"> | |
| 336 | </div> | |
| 337 | <div class="form-group"> | |
| 338 | <label class="form-label">Sort order</label> | |
| 339 | <input type="number" name="sort_order" class="input" value="0"> | |
| 340 | </div> | |
| 341 | </div> | |
| 342 | <div class="ss-grid-3"> | |
| 343 | <div class="form-group"> | |
| 344 | <label class="form-label">Delivery min days</label> | |
| 345 | <input type="number" name="delivery_days_min" class="input" placeholder="3"> | |
| 346 | </div> | |
| 347 | <div class="form-group"> | |
| 348 | <label class="form-label">Delivery max days</label> | |
| 349 | <input type="number" name="delivery_days_max" class="input" placeholder="5"> | |
| 350 | </div> | |
| 351 | <div class="form-group"> | |
| 352 | <label class="form-label">Carrier hint</label> | |
| 353 | <input type="text" name="carrier_hint" class="input" placeholder="usps"> | |
| 354 | </div> | |
| 355 | </div> | |
| 356 | <div style="display:flex;justify-content:flex-end"> | |
| 357 | <button type="submit" class="btn btn-primary">Add zone</button> | |
| 358 | </div> | |
| 359 | </form> | |
| 360 | </div> | |
| 361 | </div> | |
| 362 | ||
| 363 | <script> | |
| 364 | (function(){ | |
| 365 | // Tab switching | |
| 366 | var tabs = document.querySelectorAll('.ss-tab'); | |
| 367 | var panes = document.querySelectorAll('.ss-pane'); | |
| 368 | tabs.forEach(function(t){ | |
| 369 | t.addEventListener('click', function(){ | |
| 370 | tabs.forEach(function(x){ x.classList.remove('is-active'); }); | |
| 371 | panes.forEach(function(p){ p.classList.remove('is-active'); }); | |
| 372 | t.classList.add('is-active'); | |
| 373 | document.querySelector('.ss-pane[data-pane="'+t.dataset.tab+'"]').classList.add('is-active'); | |
| 374 | }); | |
| 375 | }); | |
| 376 | ||
| 377 | // Show carrier body when enabled toggles on (visual hint only). | |
| 378 | document.querySelectorAll('.ss-carrier-enable').forEach(function(cb){ | |
| 379 | var card = cb.closest('.ss-carrier-card'); | |
| 380 | function refresh(){ card.classList.toggle('is-on', cb.checked); } | |
| 381 | cb.addEventListener('change', refresh); | |
| 382 | refresh(); | |
| 383 | }); | |
| 384 | ||
| 385 | // Convert dollars typed in the zone table into the cents the | |
| 386 | // server expects. The Save button serializes the field value, so | |
| 387 | // we replace it pre-submit. | |
| 388 | document.querySelectorAll('input[data-cents="1"]').forEach(function(inp){ | |
| 389 | inp.closest('form').addEventListener('submit', function(){ | |
| 390 | var v = (inp.value || '').replace(/[^\d.]/g, ''); | |
| 391 | if (!v.length) return; | |
| 392 | // Already in dollar format (e.g. "9.50"); convert to cents. | |
| 393 | var dollars = parseFloat(v); | |
| 394 | if (isNaN(dollars)) { inp.value = ''; return; } | |
| 395 | inp.value = Math.round(dollars * 100); | |
| 396 | }); | |
| 397 | }); | |
| 398 | })(); | |
| 399 | </script> |