Add share-video-by-email feature

New POST /videos/{video}/share/email route (auth + throttled) handled by
VideoController@shareByEmail, sending the VideoShared mailable rendered from
emails/video-shared.blade.php. Wired into the share modal and video-actions.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-05-24 14:12:01 +03:00
1 parent a4384113c2
commit d9959c4452
6 files changed
+274 -5

No files matched your search

@@ -42,8 +42,27 @@
style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 50%; background: #0088cc; color: white; text-decoration: none;">
<i class="bi bi-telegram"></i>
</a>
<a href="#" id="shareEmailBtn" class="social-share-btn" role="button"
style="display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 50%; background: #6b7280; color: white; text-decoration: none;">
<i class="bi bi-envelope-fill"></i>
</a>
</div>
</div>
{{-- Send by email --}}
<div id="shareEmailSection" style="display:none; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border-color);">
<p style="color: var(--text-secondary); font-size: 13px; margin-bottom: 12px;">
<i class="bi bi-envelope me-1"></i> Send to a friend by email:
</p>
<input type="email" id="shareEmailTo" class="form-control" placeholder="[email protected]" autocomplete="off"
style="background: var(--bg-primary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 10px 14px; border-radius: 8px;">
<textarea id="shareEmailMsg" class="form-control" rows="2" maxlength="500" placeholder="Add a short message (optional)"
style="background: var(--bg-primary); border: 1px solid var(--border-color); color: var(--text-primary); padding: 10px 14px; border-radius: 8px; margin-top: 8px; resize: vertical;"></textarea>
<button type="button" id="shareEmailSend" class="action-btn action-btn-primary" style="width:100%; justify-content:center; margin-top:10px;">
<i class="bi bi-send"></i> <span>Send email</span>
</button>
<div id="shareEmailStatus" style="display:none; margin-top:10px; font-size:13px; text-align:center;"></div>
</div>
</div>
</div>
</div>
@@ -70,7 +89,11 @@ function _getLatestCsrf() {
return (typeof csrf !== 'undefined') ? csrf : '';
}
async function openShareModal(videoUrl, videoTitle, recordUrl) {
// Set per-open so the "Send by email" form knows the endpoint + which version to send.
var _shareEmailUrl = '';
var _shareTrack = '';
async function openShareModal(videoUrl, videoTitle, recordUrl, emailUrl) {
var csrfToken = _getLatestCsrf();
var shareUrl = videoUrl;
@@ -78,6 +101,8 @@ async function openShareModal(videoUrl, videoTitle, recordUrl) {
// share link replaces shareUrl below, so we re-attach it afterwards.
var trackParam = '';
try { trackParam = (new URL(videoUrl, window.location.origin)).searchParams.get('track') || ''; } catch (e) {}
_shareEmailUrl = emailUrl || '';
_shareTrack = trackParam;
// Obtain a unique tracked share link from the server
if (recordUrl) {
@@ -130,6 +155,17 @@ function _populateShareModal(shareUrl, videoTitle) {
copyBtn.innerHTML = '<i class="bi bi-clipboard"></i> <span>Copy</span>';
copyBtn.classList.remove('copied');
document.getElementById('copySuccess').style.display = 'none';
// Reset the email form; the envelope button only works when an endpoint was provided.
var emailBtn = document.getElementById('shareEmailBtn');
var emailSec = document.getElementById('shareEmailSection');
if (emailBtn) emailBtn.style.display = _shareEmailUrl ? 'flex' : 'none';
if (emailSec) {
emailSec.style.display = 'none';
var to = document.getElementById('shareEmailTo'); if (to) to.value = '';
var msg = document.getElementById('shareEmailMsg'); if (msg) msg.value = '';
var st = document.getElementById('shareEmailStatus'); if (st) st.style.display = 'none';
}
}
function _copyToClipboard(text) {
@@ -174,6 +210,82 @@ document.addEventListener('DOMContentLoaded', function() {
showToast('Could not copy — please copy the link manually.', 'error');
});
});
// ── Send by email ──────────────────────────────────────────────
var emailBtn = document.getElementById('shareEmailBtn');
var emailSec = document.getElementById('shareEmailSection');
var emailTo = document.getElementById('shareEmailTo');
var emailMsg = document.getElementById('shareEmailMsg');
var emailSend = document.getElementById('shareEmailSend');
var emailStat = document.getElementById('shareEmailStatus');
if (emailBtn && emailSec) {
emailBtn.addEventListener('click', function(e) {
e.preventDefault();
emailSec.style.display = (emailSec.style.display === 'none' || !emailSec.style.display) ? 'block' : 'none';
if (emailSec.style.display === 'block' && emailTo) emailTo.focus();
});
}
function _showEmailStatus(msg, color) {
if (!emailStat) return;
emailStat.textContent = msg;
emailStat.style.color = color;
emailStat.style.display = 'block';
}
if (emailSend) {
emailSend.addEventListener('click', function() {
var to = (emailTo && emailTo.value || '').trim();
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(to)) {
_showEmailStatus('Please enter a valid email address.', '#ef4444');
if (emailTo) emailTo.focus();
return;
}
if (!_shareEmailUrl) { _showEmailStatus('Email sharing is unavailable here.', '#ef4444'); return; }
emailSend.disabled = true;
var _orig = emailSend.innerHTML;
emailSend.innerHTML = '<i class="bi bi-hourglass-split"></i> <span>Sending…</span>';
_showEmailStatus('Sending…', 'var(--text-secondary)');
var body = new URLSearchParams({
_token: '{{ csrf_token() }}',
email: to,
message: (emailMsg && emailMsg.value || ''),
track: _shareTrack || '0',
});
fetch(_shareEmailUrl, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': _getLatestCsrf(),
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest',
},
body: body.toString(),
})
.then(function(r) { return r.json().then(function(d){ return { ok: r.ok, d: d }; }); })
.then(function(res) {
emailSend.disabled = false;
emailSend.innerHTML = _orig;
if (res.ok && res.d.success) {
_showEmailStatus('Sent! Your friend will get the email shortly.', '#4caf50');
if (emailTo) emailTo.value = '';
if (emailMsg) emailMsg.value = '';
setTimeout(function(){ if (emailSec) emailSec.style.display = 'none'; }, 1800);
} else {
_showEmailStatus((res.d && res.d.error) || 'Could not send the email. Please try again.', '#ef4444');
}
})
.catch(function() {
emailSend.disabled = false;
emailSend.innerHTML = _orig;
_showEmailStatus('Network error — please try again.', '#ef4444');
});
});
}
});
</script>