Add admin Pricing page with fixed and custom items
This commit is contained in:
parent
a3fd5938a5
commit
a25bf3a9c9
3 changed files with 156 additions and 1 deletions
46
app.py
46
app.py
|
|
@ -801,6 +801,52 @@ def admin_sales():
|
||||||
return render_template('admin/sales.html', sales=sales, statuses=STATUS_LABELS)
|
return render_template('admin/sales.html', sales=sales, statuses=STATUS_LABELS)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/pricing', methods=['GET', 'POST'])
|
||||||
|
def admin_pricing():
|
||||||
|
"""
|
||||||
|
Pricing configuration page.
|
||||||
|
Fixed items: one_song, both_songs, wav_per_song, stems_per_song.
|
||||||
|
Plus up to 5 custom name/price pairs.
|
||||||
|
"""
|
||||||
|
redir = require_admin()
|
||||||
|
if redir:
|
||||||
|
return redir
|
||||||
|
|
||||||
|
fixed_keys = ['one_song', 'both_songs', 'wav_per_song', 'stems_per_song']
|
||||||
|
custom_count = 5
|
||||||
|
|
||||||
|
cfg = load_booth_settings()
|
||||||
|
if 'pricing' not in cfg:
|
||||||
|
cfg['pricing'] = {}
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
pricing = {}
|
||||||
|
for key in fixed_keys:
|
||||||
|
pricing[key] = request.form.get(key, '').strip()
|
||||||
|
for i in range(1, custom_count + 1):
|
||||||
|
name = request.form.get(f'custom_name_{i}', '').strip()
|
||||||
|
price = request.form.get(f'custom_price_{i}', '').strip()
|
||||||
|
if name:
|
||||||
|
pricing[f'custom_{i}'] = {'name': name, 'price': price}
|
||||||
|
else:
|
||||||
|
pricing[f'custom_{i}'] = None
|
||||||
|
cfg['pricing'] = pricing
|
||||||
|
save_booth_settings(cfg)
|
||||||
|
flash('Pricing saved.', 'success')
|
||||||
|
return redirect(url_for('admin_pricing'))
|
||||||
|
|
||||||
|
pricing = cfg.get('pricing', {})
|
||||||
|
fixed = {key: pricing.get(key, '') for key in fixed_keys}
|
||||||
|
customs = []
|
||||||
|
for i in range(1, custom_count + 1):
|
||||||
|
entry = pricing.get(f'custom_{i}')
|
||||||
|
customs.append({
|
||||||
|
'name': entry.get('name', '') if isinstance(entry, dict) else '',
|
||||||
|
'price': entry.get('price', '') if isinstance(entry, dict) else ''
|
||||||
|
})
|
||||||
|
return render_template('admin/pricing.html', fixed=fixed, customs=customs)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admin/request/<int:rid>', methods=['GET', 'POST'])
|
@app.route('/admin/request/<int:rid>', methods=['GET', 'POST'])
|
||||||
def admin_request(rid):
|
def admin_request(rid):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,9 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- Topbar: Sales, Settings, Log out -->
|
<!-- Topbar: Pricing, Sales, Settings, Log out -->
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
|
<a href="{{ url_for('admin_pricing') }}">Pricing</a>
|
||||||
<a href="{{ url_for('admin_sales') }}">Sales</a>
|
<a href="{{ url_for('admin_sales') }}">Sales</a>
|
||||||
<a href="{{ url_for('admin_settings') }}" class="settings">Settings</a>
|
<a href="{{ url_for('admin_settings') }}" class="settings">Settings</a>
|
||||||
<a href="{{ url_for('admin_logout') }}" class="logout">Log out</a>
|
<a href="{{ url_for('admin_logout') }}" class="logout">Log out</a>
|
||||||
|
|
|
||||||
108
templates/admin/pricing.html
Normal file
108
templates/admin/pricing.html
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Pricing — Admin</title>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
||||||
|
background:#111827;
|
||||||
|
color:#f3f4f6;
|
||||||
|
margin:0;
|
||||||
|
padding:1rem;
|
||||||
|
line-height:1.5;
|
||||||
|
}
|
||||||
|
.container{max-width:800px;margin:0 auto;}
|
||||||
|
h1{color:#60a5fa;margin-top:0;}
|
||||||
|
h2{color:#93c5fd;font-size:1.1rem;margin-top:1.5rem;}
|
||||||
|
a{color:#93c5fd;}
|
||||||
|
.topbar{float:right;display:flex;gap:.75rem;align-items:center;}
|
||||||
|
label{display:block;margin-top:.8rem;font-weight:600;font-size:.9rem;}
|
||||||
|
input[type="text"]{
|
||||||
|
width:100%;
|
||||||
|
padding:.6rem;
|
||||||
|
border-radius:.5rem;
|
||||||
|
border:1px solid #374151;
|
||||||
|
background:#111827;
|
||||||
|
color:#f3f4f6;
|
||||||
|
font:inherit;
|
||||||
|
}
|
||||||
|
.price-row{
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns:1fr 1fr;
|
||||||
|
gap:.75rem;
|
||||||
|
}
|
||||||
|
button{
|
||||||
|
margin-top:1rem;
|
||||||
|
padding:.7rem 1rem;
|
||||||
|
border:none;
|
||||||
|
border-radius:.5rem;
|
||||||
|
background:#3b82f6;
|
||||||
|
color:#fff;
|
||||||
|
font-weight:700;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
button.secondary{background:#4b5563;}
|
||||||
|
.flash{
|
||||||
|
padding:.8rem;
|
||||||
|
background:#064e3b;
|
||||||
|
border-radius:.5rem;
|
||||||
|
margin-bottom:1rem;
|
||||||
|
}
|
||||||
|
.flash.error{background:#450a0a;}
|
||||||
|
.hint{font-size:.85rem;color:#9ca3af;margin-top:.25rem;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="topbar">
|
||||||
|
<a href="{{ url_for('admin_dashboard') }}">Dashboard</a>
|
||||||
|
<a href="{{ url_for('admin_sales') }}">Sales</a>
|
||||||
|
<a href="{{ url_for('admin_settings') }}">Settings</a>
|
||||||
|
<a href="{{ url_for('admin_logout') }}" style="color:#f87171">Log out</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>Pricing</h1>
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="flash {{ category }}">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<h2>Standard Items</h2>
|
||||||
|
<p class="hint">Leave blank to hide a price. Enter numbers only or include currency symbols as you prefer.</p>
|
||||||
|
|
||||||
|
<label for="one_song">One Song</label>
|
||||||
|
<input type="text" id="one_song" name="one_song" value="{{ fixed.one_song }}" placeholder="e.g. 25.00">
|
||||||
|
|
||||||
|
<label for="both_songs">Both Songs</label>
|
||||||
|
<input type="text" id="both_songs" name="both_songs" value="{{ fixed.both_songs }}" placeholder="e.g. 40.00">
|
||||||
|
|
||||||
|
<label for="wav_per_song">WAV files / song</label>
|
||||||
|
<input type="text" id="wav_per_song" name="wav_per_song" value="{{ fixed.wav_per_song }}" placeholder="e.g. 10.00">
|
||||||
|
|
||||||
|
<label for="stems_per_song">STEM files / song</label>
|
||||||
|
<input type="text" id="stems_per_song" name="stems_per_song" value="{{ fixed.stems_per_song }}" placeholder="e.g. 25.00">
|
||||||
|
|
||||||
|
<h2>Custom Items</h2>
|
||||||
|
{% for c in customs %}
|
||||||
|
<div class="price-row">
|
||||||
|
<div>
|
||||||
|
<label for="custom_name_{{ loop.index }}">Item {{ loop.index }} name</label>
|
||||||
|
<input type="text" id="custom_name_{{ loop.index }}" name="custom_name_{{ loop.index }}" value="{{ c.name }}" placeholder="e.g. Rush delivery">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="custom_price_{{ loop.index }}">Item {{ loop.index }} price</label>
|
||||||
|
<input type="text" id="custom_price_{{ loop.index }}" name="custom_price_{{ loop.index }}" value="{{ c.price }}" placeholder="e.g. 15.00">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<button type="submit">Save Pricing</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in a new issue