WebOrbiton
v1.0.0.1

StocketBase

152 lines · 6.7 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. if (count(get_included_files()) === 1) {
  6. http_response_code(403);
  7. exit;
  8. }
  9. ​
  10. if (!Auth::hasRoleAtLeast(Auth::ROLE_STORE_MANAGER)) {
  11. echo '<p>You don\'t have access to discounts.</p>';
  12. return;
  13. }
  14. ​
  15. $db = Database::site();
  16. $discounts = $db->query('SELECT * FROM discounts ORDER BY created_at DESC')->fetchAll();
  17. ​
  18. ?>
  19. <div class="dash-header-row">
  20. <h1 class="dash-title"><?= Icons::icon("tag", "icon icon-lg") ?>Discounts</h1>
  21. <button type="button" class="dash-btn dash-btn-primary" id="discount-new-btn"><?= Icons::icon('plus', 'icon icon-sm') ?>New discount</button>
  22. </div>
  23. ​
  24. <table class="dash-table">
  25. <thead>
  26. <tr>
  27. <th><?= Icons::icon('tag', 'icon icon-sm') ?>Code</th>
  28. <th><?= Icons::icon('dollar', 'icon icon-sm') ?>Value</th>
  29. <th><?= Icons::icon('checklist', 'icon icon-sm') ?>Redemptions</th>
  30. <th><?= Icons::icon('flag', 'icon icon-sm') ?>Active</th>
  31. <th></th>
  32. </tr>
  33. </thead>
  34. <tbody>
  35. <?php foreach ($discounts as $discount): ?>
  36. <tr>
  37. <td><?= htmlspecialchars($discount['code']) ?></td>
  38. <td><?= $discount['discount_type'] === 'percentage' ? (int) $discount['value'] . '%' : number_format(((int) $discount['value']) / 100, 2) . ' ' . htmlspecialchars((string) $discount['currency']) ?></td>
  39. <td><?= (int) $discount['times_redeemed'] ?><?= $discount['max_redemptions'] ? ' / ' . (int) $discount['max_redemptions'] : '' ?></td>
  40. <td><?= $discount['is_active'] ? 'Yes' : 'No' ?></td>
  41. <td class="dash-table-actions">
  42. <button type="button" class="dash-btn-small js-edit-discount"
  43. data-id="<?= (int) $discount['id'] ?>"
  44. data-code="<?= htmlspecialchars($discount['code'], ENT_QUOTES) ?>"
  45. data-description="<?= htmlspecialchars((string) $discount['description'], ENT_QUOTES) ?>"
  46. data-type="<?= htmlspecialchars($discount['discount_type'], ENT_QUOTES) ?>"
  47. data-value="<?= $discount['discount_type'] === 'percentage' ? (int) $discount['value'] : number_format(((int) $discount['value']) / 100, 2, '.', '') ?>"
  48. data-currency="<?= htmlspecialchars((string) $discount['currency'], ENT_QUOTES) ?>"
  49. data-min-order="<?= $discount['min_order_cents'] !== null ? number_format(((int) $discount['min_order_cents']) / 100, 2, '.', '') : '' ?>"
  50. data-max-redemptions="<?= htmlspecialchars((string) ($discount['max_redemptions'] ?? ''), ENT_QUOTES) ?>"
  51. data-active="<?= (int) $discount['is_active'] ?>"><?= Icons::icon('edit', 'icon icon-sm') ?>Edit</button>
  52. <form method="post" style="display:inline;" onsubmit="return confirm('Delete this discount code? Customers won\'t be able to use it anymore.');">
  53. <?= Csrf::field() ?>
  54. <input type="hidden" name="action" value="delete_discount">
  55. <input type="hidden" name="discount_id" value="<?= (int) $discount['id'] ?>">
  56. <button type="submit" class="dash-btn-small dash-btn-danger"><?= Icons::icon('trash', 'icon icon-sm') ?>Delete</button>
  57. </form>
  58. </td>
  59. </tr>
  60. <?php endforeach; ?>
  61. <?php if (empty($discounts)): ?>
  62. <tr><td colspan="5">No discount codes yet. Create one for a sale or a thank-you gift.</td></tr>
  63. <?php endif; ?>
  64. </tbody>
  65. </table>
  66. ​
  67. <div id="discount-form-wrapper" class="sidebar-box" style="max-width:480px;margin-top:20px;display:none;">
  68. <h2 class="dash-subtitle" style="margin-top:0;"><span id="discount-form-title">New discount</span></h2>
  69. <form method="post" id="discount-form">
  70. <?= Csrf::field() ?>
  71. <input type="hidden" name="action" value="save_discount">
  72. <input type="hidden" name="discount_id" id="discount_id_input" value="">
  73. ​
  74. <label>Code</label>
  75. <input type="text" name="code" id="discount_code_input" required>
  76. ​
  77. <label>Note for yourself (optional)</label>
  78. <input type="text" name="description" id="discount_description_input">
  79. ​
  80. <label>Discount type</label>
  81. <select name="discount_type" id="discount_type_input">
  82. <option value="percentage">Percent off (like 10%)</option>
  83. <option value="fixed_amount">Money off (like 20 PLN)</option>
  84. </select>
  85. ​
  86. <label>How much (a percent, or an amount like 10.00)</label>
  87. <input type="text" name="value" id="discount_value_input" required>
  88. ​
  89. <label>Currency (only for money off)</label>
  90. <input type="text" name="currency" id="discount_currency_input" maxlength="3" value="<?= htmlspecialchars(SiteFront::settings()['store_currency'] ?? 'USD') ?>">
  91. ​
  92. <label>Minimum order (optional, like 50.00)</label>
  93. <input type="text" name="min_order" id="discount_min_order_input">
  94. ​
  95. <label>How many times it can be used (optional)</label>
  96. <input type="number" name="max_redemptions" id="discount_max_redemptions_input">
  97. ​
  98. <label><input type="checkbox" name="is_active" id="discount_active_input" checked> Customers can use it now</label>
  99. ​
  100. <div class="publish-actions">
  101. <button type="submit" class="dash-btn dash-btn-primary"><?= Icons::icon('check', 'icon icon-sm') ?>Save discount</button>
  102. <button type="button" class="dash-btn" id="discount-cancel-btn"><?= Icons::icon('x', 'icon icon-sm') ?>Cancel</button>
  103. </div>
  104. </form>
  105. </div>
  106. ​
  107. <script>
  108. (function() {
  109. var wrapper = document.getElementById('discount-form-wrapper');
  110. var formTitle = document.getElementById('discount-form-title');
  111. var form = document.getElementById('discount-form');
  112. ​
  113. function resetForm() {
  114. form.reset();
  115. document.getElementById('discount_id_input').value = '';
  116. document.getElementById('discount_active_input').checked = true;
  117. formTitle.textContent = 'New discount';
  118. }
  119. ​
  120. function showForm() {
  121. wrapper.style.display = '';
  122. wrapper.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
  123. }
  124. ​
  125. document.getElementById('discount-new-btn').addEventListener('click', function() {
  126. resetForm();
  127. showForm();
  128. });
  129. ​
  130. document.getElementById('discount-cancel-btn').addEventListener('click', function() {
  131. wrapper.style.display = 'none';
  132. resetForm();
  133. });
  134. ​
  135. document.querySelectorAll('.js-edit-discount').forEach(function(button) {
  136. button.addEventListener('click', function() {
  137. document.getElementById('discount_id_input').value = this.dataset.id;
  138. document.getElementById('discount_code_input').value = this.dataset.code;
  139. document.getElementById('discount_description_input').value = this.dataset.description;
  140. document.getElementById('discount_type_input').value = this.dataset.type;
  141. document.getElementById('discount_value_input').value = this.dataset.value;
  142. document.getElementById('discount_currency_input').value = this.dataset.currency;
  143. document.getElementById('discount_min_order_input').value = this.dataset.minOrder;
  144. document.getElementById('discount_max_redemptions_input').value = this.dataset.maxRedemptions;
  145. document.getElementById('discount_active_input').checked = this.dataset.active === '1';
  146. formTitle.textContent = 'Edit discount';
  147. showForm();
  148. });
  149. });
  150. })();
  151. </script>
  152. ​