Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/TEMPLATES/webstls_shipping_settings.html
Diff

/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/TEMPLATES/webstls_shipping_settings.html

added on local at 2026-07-01 13:48:09

Added
+399
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 842377f49408
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
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 &middot; 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 &amp; 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 &amp; 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 &amp; 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 &mdash; 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">&middot; $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>
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help