added on WebSTLs (webstls.com) at 2026-07-01 22:27:04
| 1 | <!-- ===================================================================== | |
| 2 | Seller Promotions. | |
| 3 | - List view: KPI tiles + table of every promotion | |
| 4 | - New view: create form | |
| 5 | - Edit view: same form, prefilled | |
| 6 | Posts to /promotion_action.cgi for create/update/delete/activate/pause. | |
| 7 | Styles live in site.css under the .pr-* block (shared with the | |
| 8 | admin promotions template). | |
| 9 | ===================================================================== --> | |
| 10 | ||
| 11 | <div class="page-head"> | |
| 12 | <div> | |
| 13 | <span class="page-eyebrow"><span class="dot"></span> Store · Promotions</span> | |
| 14 | <h1 class="page-title">Coupons, sales & bundles</h1> | |
| 15 | <p class="page-subtitle">Create coupon codes, time-limited sales, bundles, and shareable promo links for your store. Cap redemptions by count or by date, or both.</p> | |
| 16 | </div> | |
| 17 | <div class="page-actions"> | |
| 18 | [if:$is_list][if:$not_tutorial_mode]<a href="/promotions.cgi?tutorial=1" class="btn btn-secondary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>Sample Data View</a>[/if][/if] | |
| 19 | [if:$is_list][if:$tutorial_mode]<a href="/promotions.cgi" class="btn btn-secondary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>Exit sample view</a>[/if][/if] | |
| 20 | [if:$is_list]<a href="/promotions.cgi?new=1" class="btn btn-primary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>New promotion</a>[/if] | |
| 21 | [if:$is_new]<a href="/promotions.cgi" class="btn btn-secondary">Cancel</a>[/if] | |
| 22 | [if:$is_edit]<a href="/promotions.cgi" class="btn btn-secondary">Back to list</a>[/if] | |
| 23 | </div> | |
| 24 | </div> | |
| 25 | ||
| 26 | [if:$tutorial_mode][if:$is_list] | |
| 27 | <div class="tutorial-banner"> | |
| 28 | <div class="tutorial-banner-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></div> | |
| 29 | <div class="tutorial-banner-body"> | |
| 30 | <div class="tutorial-banner-title">Tutorial · Sample data only</div> | |
| 31 | <div class="tutorial-banner-text">The five promotions below are examples to show every status (active, scheduled, expired) and every kind (coupon, bundle, flash sale). None of them are real on your store.</div> | |
| 32 | </div> | |
| 33 | <a href="/promotions.cgi" class="btn btn-secondary btn-sm">Exit sample view</a> | |
| 34 | </div> | |
| 35 | ||
| 36 | <div class="tutorial-intro"> | |
| 37 | <div class="tutorial-intro-eyebrow">A walkthrough of Promotions</div> | |
| 38 | <h2 class="tutorial-intro-title">Build coupon codes, sales, and bundles for your store.</h2> | |
| 39 | <p class="tutorial-intro-text">Four kinds of promotion: <strong>Coupons</strong> need a code at checkout, <strong>Sales</strong> auto-apply during the window, <strong>Bundles</strong> require specific products together for a bundle price, and <strong>Flash sales</strong> behave like sales but checkout treats them as urgency-priced. Each promo can have a start/end window, a redemption cap (total uses OR per-user), a custom <code>/promo/<slug></code> public link, and an optional new-customers-only filter.</p> | |
| 40 | <p class="tutorial-intro-text">When a buyer enters a code at checkout or lands on a slug link, the promo is validated against its time window, redemption cap, minimum order, and customer-filter rules, then applied to the cart. Successful redemptions are written to <code>promotion_redemptions</code> so the listing table can show <em>87 of 500 used</em> and your analytics can attribute revenue.</p> | |
| 41 | <p class="tutorial-intro-text tutorial-intro-foot">Click <a href="/promotions.cgi" class="text-brand fw-700">Exit sample view</a> to see your real promotions, or <a href="/promotions.cgi?new=1" class="text-brand fw-700">create your first one</a>.</p> | |
| 42 | </div> | |
| 43 | [/if][/if] | |
| 44 | ||
| 45 | [if:$has_flash] | |
| 46 | <div class="pr-flash $flash_kind">$flash_msg</div> | |
| 47 | [/if] | |
| 48 | ||
| 49 | [if:$schema_missing] | |
| 50 | <div class="an-banner" style="background:rgba(245,158,11,0.10);border-color:rgba(245,158,11,0.30)"> | |
| 51 | <strong>Promotions schema not yet installed.</strong> Run the latest <code>db_schema.sql</code> against your database to bring the promotion tables online. | |
| 52 | </div> | |
| 53 | [/if] | |
| 54 | ||
| 55 | [if:$is_list][if:$schema_ready] | |
| 56 | <div class="pr-kpis"> | |
| 57 | <div class="pr-kpi is-active"> | |
| 58 | <div class="lbl">Active</div> | |
| 59 | <div class="val">$cnt_active</div> | |
| 60 | </div> | |
| 61 | <div class="pr-kpi is-scheduled"> | |
| 62 | <div class="lbl">Scheduled</div> | |
| 63 | <div class="val">$cnt_scheduled</div> | |
| 64 | </div> | |
| 65 | <div class="pr-kpi is-paused"> | |
| 66 | <div class="lbl">Paused</div> | |
| 67 | <div class="val">$cnt_paused</div> | |
| 68 | </div> | |
| 69 | <div class="pr-kpi is-expired"> | |
| 70 | <div class="lbl">Expired</div> | |
| 71 | <div class="val">$cnt_expired</div> | |
| 72 | </div> | |
| 73 | </div> | |
| 74 | ||
| 75 | [if:$has_rows] | |
| 76 | <table class="pr-table"> | |
| 77 | <thead> | |
| 78 | <tr> | |
| 79 | <th>Promotion</th> | |
| 80 | <th>Kind</th> | |
| 81 | <th>Discount</th> | |
| 82 | <th>Window</th> | |
| 83 | <th>Usage</th> | |
| 84 | <th class="num">Discount given</th> | |
| 85 | <th>Status</th> | |
| 86 | <th>Actions</th> | |
| 87 | </tr> | |
| 88 | </thead> | |
| 89 | <tbody> | |
| 90 | [loop:@rows] | |
| 91 | <tr> | |
| 92 | <td> | |
| 93 | <div class="name">$loop1.name</div> | |
| 94 | <div class="meta"> | |
| 95 | [if:$loop1.code_present]Code: <code>$loop1.code</code>[/if] | |
| 96 | [if:$loop1.slug_present][if:$loop1.code_present] · [/if]Link: <code>/promo/$loop1.public_slug</code>[/if] | |
| 97 | </div> | |
| 98 | </td> | |
| 99 | <td> | |
| 100 | [if:$loop1.is_coupon]<span class="pill kind">Coupon</span>[/if] | |
| 101 | [if:$loop1.is_sale]<span class="pill kind sale">Sale</span>[/if] | |
| 102 | [if:$loop1.is_bundle]<span class="pill kind bundle">Bundle</span>[/if] | |
| 103 | [if:$loop1.is_flash]<span class="pill kind flash">Flash sale</span>[/if] | |
| 104 | </td> | |
| 105 | <td>$loop1.discount_label</td> | |
| 106 | <td> | |
| 107 | [if:$loop1.has_window] | |
| 108 | <div class="meta" style="font-size:12px;color:var(--col-text-2)"> | |
| 109 | $loop1.starts_at <br>→ $loop1.ends_at | |
| 110 | </div> | |
| 111 | [/if] | |
| 112 | [if:!$loop1.has_window]<span class="text-xs text-dim">No time limit</span>[/if] | |
| 113 | </td> | |
| 114 | <td>$loop1.cap_label</td> | |
| 115 | <td class="num">$loop1.discount_given_display</td> | |
| 116 | <td> | |
| 117 | [if:$loop1.is_active]<span class="pill active">Active</span>[/if] | |
| 118 | [if:$loop1.is_paused]<span class="pill paused">Paused</span>[/if] | |
| 119 | [if:$loop1.is_scheduled]<span class="pill scheduled">Scheduled</span>[/if] | |
| 120 | [if:$loop1.is_expired]<span class="pill expired">Expired</span>[/if] | |
| 121 | [if:$loop1.is_draft]<span class="pill draft">Draft</span>[/if] | |
| 122 | </td> | |
| 123 | <td class="actions"> | |
| 124 | <a href="$loop1.edit_url">Edit</a> | |
| 125 | [if:$loop1.is_active] | |
| 126 | <span class="sep">·</span> | |
| 127 | <form method="POST" action="/promotion_action.cgi" | |
| 128 | data-confirm-title="Pause this promotion?" | |
| 129 | data-confirm-message="It will stop applying immediately and can be resumed later." | |
| 130 | data-confirm-label="Pause promotion" | |
| 131 | data-confirm-style="warning"> | |
| 132 | <input type="hidden" name="act" value="pause"> | |
| 133 | <input type="hidden" name="id" value="$loop1.id"> | |
| 134 | <button type="submit">Pause</button> | |
| 135 | </form> | |
| 136 | [/if] | |
| 137 | [if:$loop1.is_scheduled] | |
| 138 | <span class="sep">·</span> | |
| 139 | <form method="POST" action="/promotion_action.cgi" | |
| 140 | data-confirm-title="Pause this scheduled promotion?" | |
| 141 | data-confirm-message="It will not start at the scheduled time until you reactivate it." | |
| 142 | data-confirm-label="Pause promotion" | |
| 143 | data-confirm-style="warning"> | |
| 144 | <input type="hidden" name="act" value="pause"> | |
| 145 | <input type="hidden" name="id" value="$loop1.id"> | |
| 146 | <button type="submit">Pause</button> | |
| 147 | </form> | |
| 148 | [/if] | |
| 149 | [if:$loop1.is_expired] | |
| 150 | <span class="sep">·</span> | |
| 151 | <form method="POST" action="/promotion_action.cgi" | |
| 152 | data-confirm-title="Pause this expired promotion?" | |
| 153 | data-confirm-message="It is already past its end date. Pausing locks it so it cannot be reactivated without an Edit." | |
| 154 | data-confirm-label="Pause promotion" | |
| 155 | data-confirm-style="warning"> | |
| 156 | <input type="hidden" name="act" value="pause"> | |
| 157 | <input type="hidden" name="id" value="$loop1.id"> | |
| 158 | <button type="submit">Pause</button> | |
| 159 | </form> | |
| 160 | [/if] | |
| 161 | [if:$loop1.is_paused] | |
| 162 | <span class="sep">·</span> | |
| 163 | <form method="POST" action="/promotion_action.cgi"> | |
| 164 | <input type="hidden" name="act" value="activate"> | |
| 165 | <input type="hidden" name="id" value="$loop1.id"> | |
| 166 | <button type="submit">Activate</button> | |
| 167 | </form> | |
| 168 | [/if] | |
| 169 | [if:$loop1.is_draft] | |
| 170 | <span class="sep">·</span> | |
| 171 | <form method="POST" action="/promotion_action.cgi"> | |
| 172 | <input type="hidden" name="act" value="activate"> | |
| 173 | <input type="hidden" name="id" value="$loop1.id"> | |
| 174 | <button type="submit">Activate</button> | |
| 175 | </form> | |
| 176 | [/if] | |
| 177 | <span class="sep">·</span> | |
| 178 | <form method="POST" action="/promotion_action.cgi" | |
| 179 | data-confirm-title="Delete this promotion permanently?" | |
| 180 | data-confirm-message="Redemption history is also removed. This cannot be undone." | |
| 181 | data-confirm-label="Delete promotion" | |
| 182 | data-confirm-style="danger"> | |
| 183 | <input type="hidden" name="act" value="delete"> | |
| 184 | <input type="hidden" name="id" value="$loop1.id"> | |
| 185 | <button type="submit" style="color:#fca5a5">Delete</button> | |
| 186 | </form> | |
| 187 | </td> | |
| 188 | </tr> | |
| 189 | [/loop] | |
| 190 | </tbody> | |
| 191 | </table> | |
| 192 | [/if] | |
| 193 | ||
| 194 | [if:!$has_rows] | |
| 195 | <div style="text-align:center;padding:60px 24px;background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:12px"> | |
| 196 | <div class="text-sm fw-600" style="color:#fff;margin-bottom:6px">No promotions yet</div> | |
| 197 | <div class="text-xs text-dim" style="margin-bottom:14px;max-width:360px;margin-left:auto;margin-right:auto">Create coupon codes, time-limited sales, bundles, or shareable promo links to drive sales.</div> | |
| 198 | <a href="/promotions.cgi?new=1" class="btn btn-primary btn-sm">Create your first promotion</a> | |
| 199 | </div> | |
| 200 | [/if] | |
| 201 | [/if][/if] | |
| 202 | ||
| 203 | [if:$is_new][if:$schema_ready] | |
| 204 | <form class="pr-form" method="POST" action="/promotion_action.cgi"> | |
| 205 | <input type="hidden" name="act" value="create"> | |
| 206 | <div class="pr-form-grid"> | |
| 207 | <div class="pr-field full"> | |
| 208 | <label>Name</label> | |
| 209 | <input type="text" name="name" required maxlength="160" placeholder="e.g. Spring 20% off"> | |
| 210 | <div class="hint">Internal label. Buyers do not see this.</div> | |
| 211 | </div> | |
| 212 | ||
| 213 | <div class="pr-field full"> | |
| 214 | <label>Description</label> | |
| 215 | <textarea name="description" maxlength="500" placeholder="What this promo is for, who it targets, etc."></textarea> | |
| 216 | </div> | |
| 217 | ||
| 218 | <div class="pr-field full"> | |
| 219 | <label>Kind</label> | |
| 220 | <div class="pr-radio-row"> | |
| 221 | <label class="pr-radio is-on"><input type="radio" name="kind" value="coupon" checked> Coupon (requires code)</label> | |
| 222 | <label class="pr-radio"><input type="radio" name="kind" value="sale"> Sale (auto-applies)</label> | |
| 223 | <label class="pr-radio"><input type="radio" name="kind" value="bundle"> Bundle</label> | |
| 224 | <label class="pr-radio"><input type="radio" name="kind" value="flash_sale"> Flash sale</label> | |
| 225 | </div> | |
| 226 | </div> | |
| 227 | ||
| 228 | <div class="pr-field"> | |
| 229 | <label>Discount type</label> | |
| 230 | <select name="discount_type"> | |
| 231 | <option value="percent">Percent off</option> | |
| 232 | <option value="fixed_amount">Fixed amount off</option> | |
| 233 | <option value="bundle_price">Bundle price</option> | |
| 234 | <option value="free">Free (100% off)</option> | |
| 235 | </select> | |
| 236 | </div> | |
| 237 | ||
| 238 | <div class="pr-field"> | |
| 239 | <label>Discount value</label> | |
| 240 | <div style="display:flex;gap:8px;align-items:center"> | |
| 241 | <input type="number" name="discount_value_pct" min="0" max="100" step="1" placeholder="0" style="width:80px"> | |
| 242 | <span class="hint">% off</span> | |
| 243 | <span class="hint">or</span> | |
| 244 | <input type="number" name="discount_value_amt" min="0" step="0.01" placeholder="0.00" style="width:100px"> | |
| 245 | <span class="hint">$ amount</span> | |
| 246 | </div> | |
| 247 | <div class="hint">For percent: enter 20 for 20%. For fixed/bundle: enter the dollar amount.</div> | |
| 248 | </div> | |
| 249 | ||
| 250 | <div class="pr-field"> | |
| 251 | <label>Coupon code (optional)</label> | |
| 252 | <input type="text" name="code" maxlength="64" placeholder="SPRING20" style="text-transform:uppercase"> | |
| 253 | <div class="hint">Buyers enter this at checkout. Leave blank if the promo auto-applies.</div> | |
| 254 | </div> | |
| 255 | ||
| 256 | <div class="pr-field"> | |
| 257 | <label>Public slug (optional)</label> | |
| 258 | <input type="text" name="public_slug" maxlength="64" placeholder="spring20" style="text-transform:lowercase"> | |
| 259 | <div class="hint">Becomes a shareable link: <code>webstls.com/promo/spring20</code></div> | |
| 260 | </div> | |
| 261 | ||
| 262 | <div class="pr-field"> | |
| 263 | <label>Starts at</label> | |
| 264 | <input type="datetime-local" name="starts_at_local"> | |
| 265 | </div> | |
| 266 | ||
| 267 | <div class="pr-field"> | |
| 268 | <label>Ends at</label> | |
| 269 | <input type="datetime-local" name="ends_at_local"> | |
| 270 | </div> | |
| 271 | ||
| 272 | <div class="pr-field"> | |
| 273 | <label>Max total redemptions</label> | |
| 274 | <input type="number" name="max_redemptions" min="0" placeholder="Unlimited"> | |
| 275 | <div class="hint">Leave blank for unlimited.</div> | |
| 276 | </div> | |
| 277 | ||
| 278 | <div class="pr-field"> | |
| 279 | <label>Max per buyer</label> | |
| 280 | <input type="number" name="max_per_user" min="1" value="1"> | |
| 281 | </div> | |
| 282 | ||
| 283 | <div class="pr-field"> | |
| 284 | <label>Minimum cart total</label> | |
| 285 | <input type="number" name="minimum_order" min="0" step="0.01" placeholder="0.00"> | |
| 286 | <div class="hint">Cart must exceed this before promo applies.</div> | |
| 287 | </div> | |
| 288 | ||
| 289 | <div class="pr-field"> | |
| 290 | <label>New customers only</label> | |
| 291 | <label class="pr-radio" style="display:inline-flex"><input type="checkbox" name="new_customers_only" value="1"> Only buyers with zero prior orders</label> | |
| 292 | </div> | |
| 293 | ||
| 294 | [if:$has_user_models] | |
| 295 | <div class="pr-field full"> | |
| 296 | <label>Apply to specific models (optional)</label> | |
| 297 | <div class="pr-models-box"> | |
| 298 | [loop:@user_models] | |
| 299 | <label class="pr-model-row"><input type="checkbox" name="model_ids" value="$loop1.id"> $loop1.title</label> | |
| 300 | [/loop] | |
| 301 | </div> | |
| 302 | <div class="hint">Leave all unchecked for store-wide. For Bundle kind, check the products that must all be in the cart.</div> | |
| 303 | </div> | |
| 304 | [/if] | |
| 305 | </div> | |
| 306 | ||
| 307 | <div class="pr-actions"> | |
| 308 | <button type="submit" name="publish_now" value="1" class="btn btn-primary">Create & activate</button> | |
| 309 | <button type="submit" class="btn btn-secondary">Save as draft</button> | |
| 310 | </div> | |
| 311 | </form> | |
| 312 | [/if][/if] | |
| 313 | ||
| 314 | [if:$is_edit][if:$schema_ready][if:$has_edit] | |
| 315 | <form class="pr-form" method="POST" action="/promotion_action.cgi"> | |
| 316 | <input type="hidden" name="act" value="update"> | |
| 317 | <input type="hidden" name="id" value="$edit.id"> | |
| 318 | <div class="pr-form-grid"> | |
| 319 | <div class="pr-field full"> | |
| 320 | <label>Name</label> | |
| 321 | <input type="text" name="name" required maxlength="160" value="$edit.name"> | |
| 322 | </div> | |
| 323 | ||
| 324 | <div class="pr-field full"> | |
| 325 | <label>Description</label> | |
| 326 | <textarea name="description" maxlength="500">$edit.description</textarea> | |
| 327 | </div> | |
| 328 | ||
| 329 | <div class="pr-field full"> | |
| 330 | <label>Kind</label> | |
| 331 | <div class="pr-radio-row"> | |
| 332 | <label class="pr-radio[if:$edit.is_coupon] is-on[/if]"><input type="radio" name="kind" value="coupon"[if:$edit.is_coupon] checked[/if]> Coupon</label> | |
| 333 | <label class="pr-radio[if:$edit.is_sale] is-on[/if]"><input type="radio" name="kind" value="sale"[if:$edit.is_sale] checked[/if]> Sale</label> | |
| 334 | <label class="pr-radio[if:$edit.is_bundle] is-on[/if]"><input type="radio" name="kind" value="bundle"[if:$edit.is_bundle] checked[/if]> Bundle</label> | |
| 335 | <label class="pr-radio[if:$edit.is_flash] is-on[/if]"><input type="radio" name="kind" value="flash_sale"[if:$edit.is_flash] checked[/if]> Flash sale</label> | |
| 336 | </div> | |
| 337 | </div> | |
| 338 | ||
| 339 | <div class="pr-field"> | |
| 340 | <label>Discount type</label> | |
| 341 | <select name="discount_type"> | |
| 342 | <option value="percent"[if:$edit.is_dtype_pct] selected[/if]>Percent off</option> | |
| 343 | <option value="fixed_amount"[if:$edit.is_dtype_amt] selected[/if]>Fixed amount off</option> | |
| 344 | <option value="bundle_price"[if:$edit.is_dtype_bun] selected[/if]>Bundle price</option> | |
| 345 | <option value="free"[if:$edit.is_dtype_free] selected[/if]>Free</option> | |
| 346 | </select> | |
| 347 | </div> | |
| 348 | ||
| 349 | <div class="pr-field"> | |
| 350 | <label>Discount value</label> | |
| 351 | <div style="display:flex;gap:8px;align-items:center"> | |
| 352 | <input type="number" name="discount_value_pct" min="0" max="100" step="1" value="$edit.discount_value_pct" style="width:80px"> | |
| 353 | <span class="hint">% off</span> | |
| 354 | <span class="hint">or</span> | |
| 355 | <input type="number" name="discount_value_amt" min="0" step="0.01" value="$edit.discount_value_amt" style="width:100px"> | |
| 356 | <span class="hint">\$ amount</span> | |
| 357 | </div> | |
| 358 | </div> | |
| 359 | ||
| 360 | <div class="pr-field"> | |
| 361 | <label>Coupon code</label> | |
| 362 | <input type="text" name="code" maxlength="64" value="$edit.code" style="text-transform:uppercase"> | |
| 363 | </div> | |
| 364 | ||
| 365 | <div class="pr-field"> | |
| 366 | <label>Public slug</label> | |
| 367 | <input type="text" name="public_slug" maxlength="64" value="$edit.public_slug" style="text-transform:lowercase"> | |
| 368 | </div> | |
| 369 | ||
| 370 | <div class="pr-field"> | |
| 371 | <label>Starts at</label> | |
| 372 | <input type="datetime-local" name="starts_at_local" value="$edit.starts_at_local"> | |
| 373 | </div> | |
| 374 | ||
| 375 | <div class="pr-field"> | |
| 376 | <label>Ends at</label> | |
| 377 | <input type="datetime-local" name="ends_at_local" value="$edit.ends_at_local"> | |
| 378 | </div> | |
| 379 | ||
| 380 | <div class="pr-field"> | |
| 381 | <label>Max total redemptions</label> | |
| 382 | <input type="number" name="max_redemptions" min="0" value="$edit.max_redemptions" placeholder="Unlimited"> | |
| 383 | </div> | |
| 384 | ||
| 385 | <div class="pr-field"> | |
| 386 | <label>Max per buyer</label> | |
| 387 | <input type="number" name="max_per_user" min="1" value="$edit.max_per_user"> | |
| 388 | </div> | |
| 389 | ||
| 390 | <div class="pr-field"> | |
| 391 | <label>Minimum cart total</label> | |
| 392 | <input type="number" name="minimum_order" min="0" step="0.01" value="$edit.minimum_order"> | |
| 393 | </div> | |
| 394 | ||
| 395 | <div class="pr-field"> | |
| 396 | <label>New customers only</label> | |
| 397 | <label class="pr-radio" style="display:inline-flex"><input type="checkbox" name="new_customers_only" value="1"[if:$edit.new_customers_only] checked[/if]> Only buyers with zero prior orders</label> | |
| 398 | </div> | |
| 399 | ||
| 400 | [if:$has_user_models] | |
| 401 | <div class="pr-field full"> | |
| 402 | <label>Apply to specific models</label> | |
| 403 | <div class="pr-models-box"> | |
| 404 | [loop:@user_models] | |
| 405 | <label class="pr-model-row"><input type="checkbox" name="model_ids" value="$loop1.id"[if:$loop1.is_selected] checked[/if]> $loop1.title</label> | |
| 406 | [/loop] | |
| 407 | </div> | |
| 408 | </div> | |
| 409 | [/if] | |
| 410 | </div> | |
| 411 | ||
| 412 | <div class="pr-actions"> | |
| 413 | <button type="submit" class="btn btn-primary">Save changes</button> | |
| 414 | <a href="/promotions.cgi" class="btn btn-secondary">Cancel</a> | |
| 415 | </div> | |
| 416 | </form> | |
| 417 | [/if][/if][/if] | |
| 418 | ||
| 419 | <div class="adm-foot-spacer" data-end="promotions"> </div> |