diff --git a/app.py b/app.py index 1778556..519ca30 100644 --- a/app.py +++ b/app.py @@ -234,11 +234,29 @@ def revise(token): Customer asked for changes. Store the note and reset status to 'songs_uploaded' so the operator sees it in the dashboard queue. """ - req = get_request_by_token(token) - if not req: - abort(404) note = request.form.get('revision_note', '').strip() - update_request(req['id'], revision_note=note, status='revisions_requested') + # Increment revision counter and archive current files before new versions are uploaded. + req = get_request_by_token(token) + if req: + new_count = (req.get('revision_count') or 0) + 1 + upload_dir = Path(current_app.config['UPLOAD_FOLDER']) / str(req['id']) + if upload_dir.exists(): + for field, version in (('song_a_path', 'A'), ('song_b_path', 'B')): + path = req.get(field) + if path and Path(path).exists(): + old = Path(path) + archived = upload_dir / f"Rev{new_count}-{old.name}" + try: + old.rename(archived) + # Keep path pointing to the archived file; operator will upload new files. + req[field] = str(archived) + except OSError: + pass + update_request(req['id'], revision_note=note, status='revisions_requested', + song_a_path=req.get('song_a_path'), song_b_path=req.get('song_b_path'), + customer_approved='none', revision_count=new_count) + else: + abort(404) # NOTE: No operator email is sent; the dashboard is the single queue. flash('Your feedback has been saved. We will regenerate and update you.', 'success') @@ -323,6 +341,16 @@ def admin_request(rid): def basename(path): return Path(path).name if path else '' + # Collect any extra MP3 files in the request folder (archived revisions). + upload_dir = Path(current_app.config['UPLOAD_FOLDER']) / str(rid) + current_paths = {req['song_a_path'], req['song_b_path']} + extra_files = [] + if upload_dir.exists(): + for f in upload_dir.iterdir(): + if f.is_file() and f.suffix.lower() == '.mp3' and str(f) not in current_paths: + extra_files.append(str(f)) + extra_files.sort() + if request.method == 'POST': action = request.form.get('action') @@ -366,34 +394,35 @@ def admin_request(rid): elif action == 'mark_paid_deliver': # Finalize: record Square payment ref, attach approved MP3s, email customer. - if req['customer_approved'] == 'none': - flash('Customer has not approved a version yet.', 'error') - else: - payment_ref = request.form.get('square_payment_ref', '').strip() - if not payment_ref: - flash('Square payment reference is required.', 'error') - return redirect(url_for('admin_request', rid=rid)) + payment_ref = request.form.get('square_payment_ref', '').strip() + if not payment_ref: + flash('Square payment reference is required.', 'error') + return redirect(url_for('admin_request', rid=rid)) - attachments = [] - if req['customer_approved'] in ('a', 'both') and req['song_a_path']: - a_name = Path(req['song_a_path']).name - attachments.append((req['song_a_path'], a_name)) - if req['customer_approved'] in ('b', 'both') and req['song_b_path']: - b_name = Path(req['song_b_path']).name - attachments.append((req['song_b_path'], b_name)) + # Build list of selected files from checkboxes. + selected = request.form.getlist('deliver_file') + if not selected: + flash('Select at least one file to deliver.', 'error') + return redirect(url_for('admin_request', rid=rid)) - player_link = f"{current_app.config['PUBLIC_BASE_URL']}/play/{req['player_token']}" - body = f"Hi {req['name']},\n\nThanks for your payment! Your approved song is attached to this email.\n\nIf you selected both versions, you'll find two MP3 files.\n\nYou can also keep streaming them here: {player_link}\n\nEnjoy!\n\n— {current_app.config['BOOTH_NAME']}" - try: - send_email(req['email'], 'Your theme song files are here!', body, attachments=attachments) - update_request(rid, square_payment_ref=payment_ref, delivery_sent_at=now_utc(), status='delivered') - flash('Delivery email sent with MP3 attachments.', 'success') - except Exception as e: - flash(f'Failed to send delivery email: {e}', 'error') + attachments = [] + for path in selected: + p = Path(path) + if p.exists(): + attachments.append((str(p), p.name)) + + player_link = f"{current_app.config['PUBLIC_BASE_URL']}/play/{req['player_token']}" + body = f"Hi {req['name']},\n\nThanks for your payment! Your selected song(s) are attached to this email.\n\nYou can also keep streaming them here: {player_link}\n\nEnjoy!\n\n— {current_app.config['BOOTH_NAME']}" + try: + send_email(req['email'], 'Your theme song files are here!', body, attachments=attachments) + update_request(rid, square_payment_ref=payment_ref, delivery_sent_at=now_utc(), status='delivered') + flash('Delivery email sent with MP3 attachments.', 'success') + except Exception as e: + flash(f'Failed to send delivery email: {e}', 'error') return redirect(url_for('admin_request', rid=rid)) - return render_template('admin/request.html', req=req, statuses=STATUS_LABELS, file_exists=file_exists, basename=basename) + return render_template('admin/request.html', req=req, statuses=STATUS_LABELS, file_exists=file_exists, basename=basename, extra_files=extra_files) @app.route('/admin/request//delete', methods=['POST']) diff --git a/models.py b/models.py index 8ca712f..bf651fc 100644 --- a/models.py +++ b/models.py @@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS requests ( square_payment_ref TEXT, admin_alert_email TEXT, player_token TEXT NOT NULL UNIQUE, + revision_count INTEGER DEFAULT 0, revision_note TEXT ); diff --git a/templates/admin/request.html b/templates/admin/request.html index dbc24ee..f6752c0 100644 --- a/templates/admin/request.html +++ b/templates/admin/request.html @@ -97,6 +97,26 @@ margin-top:0; color:#f87171; } + .file-select{ + background:#111827; + padding:.6rem; + border-radius:.5rem; + margin:.3rem 0; + } + .file-select input{ + width:auto; + margin-right:.5rem; + } + .file-select label{ + display:inline; + margin:0; + font-weight:400; + } + .old-rev{ + font-size:.85rem; + color:#9ca3af; + margin-left:1.8rem; + } @@ -125,7 +145,7 @@ {% if req.revision_note %}
-

šŸ“ Revisions Requested

+

šŸ“ Revisions Requested{% if req.revision_count %}Revision #{{ req.revision_count }}{% endif %}

{{ req.revision_note }}

{% endif %} @@ -238,10 +258,36 @@

+ +

Select files to deliver:

+ + {% set all_files = [] %} + {% if req.song_a_path and file_exists(req.song_a_path) %} + {% set _ = all_files.append(req.song_a_path) %} + {% endif %} + {% if req.song_b_path and file_exists(req.song_b_path) %} + {% set _ = all_files.append(req.song_b_path) %} + {% endif %} + + + {% set files_to_show = all_files + extra_files %} + {% for fpath in files_to_show %} +
+ + + {% if not (fpath == req.song_a_path or fpath == req.song_b_path) %} +
archived / revision file
+ {% endif %} +
+ {% else %} +

No files available. Upload songs first.

+ {% endfor %} +
- +
diff --git a/templates/player.html b/templates/player.html index e7db9ca..ac9b6fb 100644 --- a/templates/player.html +++ b/templates/player.html @@ -8,7 +8,9 @@ /* Private customer player page. Shows two audio players for Version A and Version B, - plus approval buttons and a revision note form. + plus approval buttons or a revision note form. + After the customer makes a choice, the controls are hidden + and a confirmation/waiting message is shown instead. */ body{ font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; @@ -45,6 +47,7 @@ font-weight:700; cursor:pointer; } + button:disabled{background:#374151;color:#9ca3af;cursor:not-allowed;} button.selected{background:#10b981;} button.both{background:#8b5cf6;} button.revision{background:#f59e0b;color:#000;} @@ -66,6 +69,15 @@ margin-top:1rem; } .status.waiting{background:#3f3f46;} + .locked{ + margin-top:1rem; + padding:1rem; + background:#111827; + border-radius:.5rem; + border:1px solid #374151; + } + .locked h2{margin-top:0;color:#fbbf24;} + .locked p{margin:.3rem 0;} @@ -85,14 +97,36 @@ - {% if req.status in ['songs_uploaded','revisions_requested','awaiting_payment','paid','delivered'] %} + {% if req.status == 'revisions_requested' %} + +
+

šŸ“ Revision Requested

+

You asked for changes. We will generate a new version and update this page.

+

Your note: {{ req.revision_note }}

+
+ + {% elif req.status in ['awaiting_payment','paid','delivered'] %} + +
+

āœ… Choice Received

+

You selected: {% if req.customer_approved == 'both' %}Both Versions{% else %}Version {{ req.customer_approved.upper() }}{% endif %}

+ {% if req.status == 'awaiting_payment' %} +

Please return to the booth to finalize payment and collect your files.

+ {% elif req.status == 'paid' %} +

Payment recorded. Your files are being prepared.

+ {% elif req.status == 'delivered' %} +

Delivered! Check your email for the MP3 attachment(s).

+ {% endif %} +
+ + {% else %}
- - - + + +
@@ -106,20 +140,6 @@ {% endif %} - {% if req.status == 'awaiting_payment' %} - -
- Thanks for choosing {{ req.customer_approved.upper() }}! Please head to the booth to finalize payment and collect your files. -
- {% endif %} - - {% if req.status == 'delivered' %} - -
- Delivered! āœ… Check your email for the MP3 attachment(s). -
- {% endif %} -