Switch price inputs from cents to dollars
All creator-facing price inputs now accept dollar values with
auto-conversion to cents via hidden fields:
- PWYW minimum price (item pricing tab)
- Subscription tier monthly price (project subscriptions tab)
- Promo code fixed discount (project promotions tab)
- Inline item price edit (item edit row)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 files changed,
+60 insertions,
-14 deletions
| 38 |
38 |
|
### High (significant friction reduction)
|
| 39 |
39 |
|
|
| 40 |
40 |
|
- [ ] **[MEDIUM]** Restructure user dashboard tabs — show 4 core tabs (Account, Projects, Payments, Support) by default. Collapse SyncKit, SSH Keys, Forums, Media into "More Tools" overflow section. Current 11 tabs overwhelm new creators
|
| 41 |
|
- |
- [ ] **[MEDIUM]** Fix price input to use dollars, not cents — promo code and subscription tier forms accept cents (e.g. "500" for $5). Add live preview ("= $5.00") or switch to dollar input with auto-conversion
|
|
41 |
+ |
- [x] **[MEDIUM]** Fix price input to use dollars, not cents — PWYW min, subscription tier, promo code fixed discount, and inline item edit all now accept dollars with auto-conversion to cents
|
| 42 |
42 |
|
- [x] **[MEDIUM]** Standardize pricing terminology — item wizard now says "One-Time Purchase" to match project wizard, paywall, landing page, and docs
|
| 43 |
43 |
|
- [x] **[MEDIUM]** Add self-service refund UI for creators — new "Sales" tab on item dashboard with per-transaction refund buttons, Stripe refund API integration
|
| 44 |
44 |
|
|
| 13 |
13 |
|
</select>
|
| 14 |
14 |
|
</td>
|
| 15 |
15 |
|
<td>
|
| 16 |
|
- |
<input type="number" name="price_cents" value="" placeholder="0" class="edit-input" style="width: 80px;">
|
|
16 |
+ |
<input type="number" step="0.01" min="0" placeholder="0.00" class="edit-input" style="width: 80px;"
|
|
17 |
+ |
oninput="this.nextElementSibling.value = Math.round(parseFloat(this.value || 0) * 100)">
|
|
18 |
+ |
<input type="hidden" name="price_cents" value="0">
|
| 17 |
19 |
|
</td>
|
| 18 |
20 |
|
<td>{{ item.sales }}</td>
|
| 19 |
21 |
|
<td>{{ item.revenue }}</td>
|
| 21 |
21 |
|
|
| 22 |
22 |
|
<div id="pwyw-settings" style="{% if !item.pwyw_enabled %}display: none;{% endif %}">
|
| 23 |
23 |
|
<div class="form-group">
|
| 24 |
|
- |
<label for="pwyw-min">Minimum price (cents)</label>
|
| 25 |
|
- |
<input type="number" id="pwyw-min" name="pwyw_min_cents"
|
| 26 |
|
- |
value="{% if let Some(min) = item.pwyw_min_cents %}{{ min }}{% endif %}"
|
| 27 |
|
- |
min="0" placeholder="0 (any amount)">
|
| 28 |
|
- |
<p class="form-hint">The lowest amount a buyer can pay, in cents. 0 = any amount.</p>
|
|
24 |
+ |
<label for="pwyw-min-dollars">Minimum price ($)</label>
|
|
25 |
+ |
<input type="number" id="pwyw-min-dollars" step="0.01" min="0" placeholder="0.00">
|
|
26 |
+ |
<input type="hidden" id="pwyw-min-cents" name="pwyw_min_cents"
|
|
27 |
+ |
value="{% if let Some(min) = item.pwyw_min_cents %}{{ min }}{% endif %}">
|
|
28 |
+ |
<p class="form-hint">The lowest amount a buyer can pay. $0 = any amount.</p>
|
| 29 |
29 |
|
</div>
|
| 30 |
30 |
|
</div>
|
|
31 |
+ |
<script>
|
|
32 |
+ |
(function() {
|
|
33 |
+ |
var dollars = document.getElementById('pwyw-min-dollars');
|
|
34 |
+ |
var cents = document.getElementById('pwyw-min-cents');
|
|
35 |
+ |
if (cents.value) dollars.value = (parseInt(cents.value, 10) / 100).toFixed(2);
|
|
36 |
+ |
dollars.addEventListener('input', function() {
|
|
37 |
+ |
cents.value = Math.round(parseFloat(this.value || 0) * 100);
|
|
38 |
+ |
});
|
|
39 |
+ |
})();
|
|
40 |
+ |
</script>
|
| 31 |
41 |
|
|
| 32 |
42 |
|
<button class="primary" type="submit">
|
| 33 |
43 |
|
Save PWYW Settings
|
| 38 |
38 |
|
</select>
|
| 39 |
39 |
|
</div>
|
| 40 |
40 |
|
<div class="form-group" style="margin-bottom: 0;">
|
| 41 |
|
- |
<label for="pc-value" style="font-size: 0.85rem;">Value</label>
|
| 42 |
|
- |
<input type="number" id="pc-value" name="discount_value" min="1"
|
|
41 |
+ |
<label for="pc-value-input" style="font-size: 0.85rem;">Value</label>
|
|
42 |
+ |
<input type="number" id="pc-value-input" min="1" step="1"
|
| 43 |
43 |
|
placeholder="e.g. 50" style="width: 100px;">
|
|
44 |
+ |
<input type="hidden" id="pc-value-hidden" name="discount_value">
|
| 44 |
45 |
|
</div>
|
| 45 |
46 |
|
</div>
|
| 46 |
47 |
|
<div id="trial-fields" class="form-group" style="margin-bottom: 0; display: none;">
|
| 70 |
71 |
|
{% endif %}
|
| 71 |
72 |
|
<button class="secondary" type="submit" style="height: fit-content;">Create</button>
|
| 72 |
73 |
|
</div>
|
| 73 |
|
- |
<p class="form-hint" style="margin-top: 0.5rem;">For percentage: enter 1-100. For fixed: enter amount in cents (e.g. 500 = $5.00). Free access codes auto-generate if left blank.</p>
|
|
74 |
+ |
<p class="form-hint" style="margin-top: 0.5rem;">For percentage: enter 1-100. For fixed: enter dollar amount (e.g. 5 = $5.00 off). Free access codes auto-generate if left blank.</p>
|
|
75 |
+ |
<script>
|
|
76 |
+ |
(function() {
|
|
77 |
+ |
var typeSelect = document.getElementById('pc-type');
|
|
78 |
+ |
var input = document.getElementById('pc-value-input');
|
|
79 |
+ |
var hidden = document.getElementById('pc-value-hidden');
|
|
80 |
+ |
function updatePromoInput() {
|
|
81 |
+ |
if (typeSelect.value === 'fixed') {
|
|
82 |
+ |
input.step = '0.01';
|
|
83 |
+ |
input.placeholder = 'e.g. 5.00';
|
|
84 |
+ |
} else {
|
|
85 |
+ |
input.step = '1';
|
|
86 |
+ |
input.placeholder = 'e.g. 50';
|
|
87 |
+ |
}
|
|
88 |
+ |
syncValue();
|
|
89 |
+ |
}
|
|
90 |
+ |
function syncValue() {
|
|
91 |
+ |
var val = parseFloat(input.value || 0);
|
|
92 |
+ |
hidden.value = typeSelect.value === 'fixed' ? Math.round(val * 100) : Math.round(val);
|
|
93 |
+ |
}
|
|
94 |
+ |
typeSelect.addEventListener('change', updatePromoInput);
|
|
95 |
+ |
input.addEventListener('input', syncValue);
|
|
96 |
+ |
updatePromoInput();
|
|
97 |
+ |
})();
|
|
98 |
+ |
</script>
|
| 74 |
99 |
|
</form>
|
| 75 |
100 |
|
</details>
|
| 76 |
101 |
|
|
| 28 |
28 |
|
<textarea id="tier-description" name="description" placeholder="What subscribers get..." maxlength="2000"></textarea>
|
| 29 |
29 |
|
</div>
|
| 30 |
30 |
|
<div class="form-group">
|
| 31 |
|
- |
<label for="tier-price">Monthly Price (USD)</label>
|
| 32 |
|
- |
<input type="number" id="tier-price" name="price_cents" min="100" step="1" placeholder="500" required
|
| 33 |
|
- |
title="Price in cents (e.g. 500 = $5.00)">
|
| 34 |
|
- |
<span style="font-size: 0.85rem; opacity: 0.6;">In cents. Minimum $1.00 (100 cents).</span>
|
|
31 |
+ |
<label for="tier-price-dollars">Monthly Price ($)</label>
|
|
32 |
+ |
<input type="number" id="tier-price-dollars" step="0.01" min="1.00" placeholder="5.00" required>
|
|
33 |
+ |
<input type="hidden" id="tier-price-cents" name="price_cents">
|
|
34 |
+ |
<span style="font-size: 0.85rem; opacity: 0.6;">Minimum $1.00.</span>
|
| 35 |
35 |
|
</div>
|
|
36 |
+ |
<script>
|
|
37 |
+ |
(function() {
|
|
38 |
+ |
var dollars = document.getElementById('tier-price-dollars');
|
|
39 |
+ |
var cents = document.getElementById('tier-price-cents');
|
|
40 |
+ |
dollars.addEventListener('input', function() {
|
|
41 |
+ |
cents.value = Math.round(parseFloat(this.value || 0) * 100);
|
|
42 |
+ |
});
|
|
43 |
+ |
})();
|
|
44 |
+ |
</script>
|
| 36 |
45 |
|
<button class="primary" type="submit">Create Tier</button>
|
| 37 |
46 |
|
</form>
|
| 38 |
47 |
|
</details>
|