Spaces:
Sleeping
Sleeping
prince1604 commited on
Commit ·
4371a7d
1
Parent(s): 42cddc3
Fix silent JS errors and enable multi-file attachment selection
Browse files- app.py +6 -3
- templates/campaign.html +43 -6
app.py
CHANGED
|
@@ -55,11 +55,14 @@ def setup_campaign():
|
|
| 55 |
sender_password = request.form.get('sender_password')
|
| 56 |
subject = request.form.get('subject')
|
| 57 |
body = request.form.get('body')
|
| 58 |
-
email_column = request.form.get('email_column', '
|
| 59 |
contacts_file = request.files.get('contactsFile')
|
| 60 |
|
| 61 |
if not contacts_file or contacts_file.filename == '':
|
| 62 |
return jsonify({'error': 'Contacts file is required.'}), 400
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
# Secure filename and check extension
|
| 65 |
allowed_extensions = {'.csv', '.xlsx', '.xls'}
|
|
@@ -77,7 +80,7 @@ def setup_campaign():
|
|
| 77 |
|
| 78 |
# Verify SMTP credentials BEFORE starting campaign
|
| 79 |
try:
|
| 80 |
-
server = smtplib.SMTP('smtp.gmail.com', 587)
|
| 81 |
server.starttls()
|
| 82 |
server.login(sender_email, sender_password)
|
| 83 |
server.quit()
|
|
@@ -241,7 +244,7 @@ def _send_single_email(s_name, s_email, s_pass, to_email, subj, text, att_paths)
|
|
| 241 |
part.add_header('Content-Disposition', f'attachment; filename="{os.path.basename(p)}"')
|
| 242 |
msg.attach(part)
|
| 243 |
|
| 244 |
-
server = smtplib.SMTP('smtp.gmail.com', 587)
|
| 245 |
server.starttls()
|
| 246 |
server.login(s_email, s_pass)
|
| 247 |
server.send_message(msg)
|
|
|
|
| 55 |
sender_password = request.form.get('sender_password')
|
| 56 |
subject = request.form.get('subject')
|
| 57 |
body = request.form.get('body')
|
| 58 |
+
email_column = request.form.get('email_column', '').strip()
|
| 59 |
contacts_file = request.files.get('contactsFile')
|
| 60 |
|
| 61 |
if not contacts_file or contacts_file.filename == '':
|
| 62 |
return jsonify({'error': 'Contacts file is required.'}), 400
|
| 63 |
+
|
| 64 |
+
if not email_column:
|
| 65 |
+
return jsonify({'error': 'Please select an Email Column.'}), 400
|
| 66 |
|
| 67 |
# Secure filename and check extension
|
| 68 |
allowed_extensions = {'.csv', '.xlsx', '.xls'}
|
|
|
|
| 80 |
|
| 81 |
# Verify SMTP credentials BEFORE starting campaign
|
| 82 |
try:
|
| 83 |
+
server = smtplib.SMTP('smtp.gmail.com', 587, timeout=15)
|
| 84 |
server.starttls()
|
| 85 |
server.login(sender_email, sender_password)
|
| 86 |
server.quit()
|
|
|
|
| 244 |
part.add_header('Content-Disposition', f'attachment; filename="{os.path.basename(p)}"')
|
| 245 |
msg.attach(part)
|
| 246 |
|
| 247 |
+
server = smtplib.SMTP('smtp.gmail.com', 587, timeout=15)
|
| 248 |
server.starttls()
|
| 249 |
server.login(s_email, s_pass)
|
| 250 |
server.send_message(msg)
|
templates/campaign.html
CHANGED
|
@@ -480,6 +480,8 @@ LinkedIn: {LinkedInURL}`
|
|
| 480 |
|
| 481 |
// Handle both forms dynamically depending on which is visible
|
| 482 |
document.querySelectorAll('.campaign-form').forEach(form => {
|
|
|
|
|
|
|
| 483 |
const fileInput = form.querySelector('.contactsFileInput');
|
| 484 |
const fileNameDisp = form.querySelector('.contactFileName');
|
| 485 |
if (fileInput && fileNameDisp) {
|
|
@@ -543,9 +545,20 @@ LinkedIn: {LinkedInURL}`
|
|
| 543 |
if (attachInput && attachCountDisp) {
|
| 544 |
attachInput.addEventListener('change', e => {
|
| 545 |
if (e.target.files) {
|
| 546 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
}
|
| 548 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
}
|
| 550 |
|
| 551 |
form.addEventListener('submit', async (e) => {
|
|
@@ -563,6 +576,18 @@ LinkedIn: {LinkedInURL}`
|
|
| 563 |
|
| 564 |
try {
|
| 565 |
const formData = new FormData(form);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
const res = await fetch('/api/setup_campaign', {
|
| 567 |
method: 'POST',
|
| 568 |
body: formData
|
|
@@ -611,8 +636,14 @@ LinkedIn: {LinkedInURL}`
|
|
| 611 |
sendBatch(campaignId);
|
| 612 |
}
|
| 613 |
} catch (err) {
|
| 614 |
-
if (progressStatus)
|
| 615 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
if (submitBtn) submitBtn.disabled = false;
|
| 617 |
if (btnSpan) btnSpan.textContent = 'Retry Send';
|
| 618 |
}
|
|
@@ -621,10 +652,16 @@ LinkedIn: {LinkedInURL}`
|
|
| 621 |
await sendBatch(data.campaign_id);
|
| 622 |
|
| 623 |
} catch (err) {
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
if (submitBtn) submitBtn.disabled = false;
|
| 626 |
-
if (btnSpan) btnSpan.textContent = '
|
| 627 |
-
if (progressContainer) progressContainer.classList.add('hidden-forced');
|
| 628 |
}
|
| 629 |
});
|
| 630 |
});
|
|
|
|
| 480 |
|
| 481 |
// Handle both forms dynamically depending on which is visible
|
| 482 |
document.querySelectorAll('.campaign-form').forEach(form => {
|
| 483 |
+
let selectedAttachments = [];
|
| 484 |
+
|
| 485 |
const fileInput = form.querySelector('.contactsFileInput');
|
| 486 |
const fileNameDisp = form.querySelector('.contactFileName');
|
| 487 |
if (fileInput && fileNameDisp) {
|
|
|
|
| 545 |
if (attachInput && attachCountDisp) {
|
| 546 |
attachInput.addEventListener('change', e => {
|
| 547 |
if (e.target.files) {
|
| 548 |
+
for(let i=0; i<e.target.files.length; i++) {
|
| 549 |
+
selectedAttachments.push(e.target.files[i]);
|
| 550 |
+
}
|
| 551 |
+
attachCountDisp.textContent = selectedAttachments.length + " files attached (Tap to reset)";
|
| 552 |
+
attachInput.value = ''; // Reset so they can add more
|
| 553 |
}
|
| 554 |
});
|
| 555 |
+
|
| 556 |
+
// Allow resetting attachments by clicking the count
|
| 557 |
+
attachCountDisp.style.cursor = 'pointer';
|
| 558 |
+
attachCountDisp.addEventListener('click', () => {
|
| 559 |
+
selectedAttachments = [];
|
| 560 |
+
attachCountDisp.textContent = "0 files attached";
|
| 561 |
+
});
|
| 562 |
}
|
| 563 |
|
| 564 |
form.addEventListener('submit', async (e) => {
|
|
|
|
| 576 |
|
| 577 |
try {
|
| 578 |
const formData = new FormData(form);
|
| 579 |
+
|
| 580 |
+
// Inject accumulated attachments
|
| 581 |
+
formData.delete('attachments');
|
| 582 |
+
selectedAttachments.forEach(file => {
|
| 583 |
+
formData.append('attachments', file);
|
| 584 |
+
});
|
| 585 |
+
|
| 586 |
+
// Ensure email_column is safely handled
|
| 587 |
+
if (!formData.get('email_column')) {
|
| 588 |
+
throw new Error("Please select an Email Column after uploading your contacts file.");
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
const res = await fetch('/api/setup_campaign', {
|
| 592 |
method: 'POST',
|
| 593 |
body: formData
|
|
|
|
| 636 |
sendBatch(campaignId);
|
| 637 |
}
|
| 638 |
} catch (err) {
|
| 639 |
+
if (progressStatus) {
|
| 640 |
+
progressStatus.textContent = "Network Error while sending: " + err.message;
|
| 641 |
+
progressStatus.classList.add("text-red-600");
|
| 642 |
+
}
|
| 643 |
+
if (progressTitle) {
|
| 644 |
+
progressTitle.textContent = "Error Sending!";
|
| 645 |
+
progressTitle.classList.replace("text-primary", "text-red-600");
|
| 646 |
+
}
|
| 647 |
if (submitBtn) submitBtn.disabled = false;
|
| 648 |
if (btnSpan) btnSpan.textContent = 'Retry Send';
|
| 649 |
}
|
|
|
|
| 652 |
await sendBatch(data.campaign_id);
|
| 653 |
|
| 654 |
} catch (err) {
|
| 655 |
+
if (progressTitle) {
|
| 656 |
+
progressTitle.textContent = "Setup Error";
|
| 657 |
+
progressTitle.classList.replace('text-primary', 'text-red-600');
|
| 658 |
+
}
|
| 659 |
+
if (progressStatus) {
|
| 660 |
+
progressStatus.textContent = err.message;
|
| 661 |
+
progressStatus.classList.add('text-red-600', 'font-bold');
|
| 662 |
+
}
|
| 663 |
if (submitBtn) submitBtn.disabled = false;
|
| 664 |
+
if (btnSpan) btnSpan.textContent = 'Retry';
|
|
|
|
| 665 |
}
|
| 666 |
});
|
| 667 |
});
|