Add sports-match type, device tracking, profile visits, and share refactor

- New SportsMatch model/controller and sports UI components/modal
- Move share-modal to a reusable x-share-modal/x-share-button component
- Add VideoSharedWithUser notification and share-to-members flow
- Device/user-agent tracking on views, downloads, share accesses
- ProfileVisit model + migration; subscription source tracking
- Email thumbnail support; remove stale TODO files
This commit is contained in:
ghassan committed 2026-05-29 01:50:28 +03:00
1 parent 6aae6f86b6
commit 73527f3781
61 files changed
+4014 -636

No files matched your search

@@ -139,6 +139,7 @@
class="action-btn desktop-action subscribe-toggle-btn {{ $isSubscribed ? 'subscribed' : '' }}"
data-channel-id="{{ $video->user_id }}"
data-subscribe-url="{{ route('channel.subscribe', $video->user_id) }}"
data-source-video-id="{{ $video->id }}"
data-subscribed="{{ $isSubscribed ? 'true' : 'false' }}">
<i class="bi {{ $isSubscribed ? 'bi-bell-fill' : 'bi-bell' }}"></i>
<span class="subscribe-label">{{ $isSubscribed ? 'Subscribed' : 'Subscribe' }}</span>
@@ -168,7 +169,7 @@
@if ($video->isShareable())
<button class="action-btn desktop-action"
onclick="shareCurrent('{{ $video->share_url }}', '{{ addslashes($video->title) }}', '{{ route('videos.recordShare', $video) }}', '{{ Auth::check() ? route('videos.shareEmail', $video) : '' }}')">
onclick="shareCurrent('{{ $video->share_url }}', '{{ addslashes($video->title) }}', '{{ route('videos.recordShare', $video) }}', '{{ Auth::check() ? route('videos.shareEmail', $video) : '' }}', '{{ Auth::check() ? route('videos.shareMembers', $video) : '' }}')">
<i class="bi bi-share"></i><span>Share</span>
</button>
@endif
@@ -238,6 +239,7 @@
class="dropdown-item subscribe-toggle-btn {{ $isSubscribed ? 'subscribed' : '' }}"
data-channel-id="{{ $video->user_id }}"
data-subscribe-url="{{ route('channel.subscribe', $video->user_id) }}"
data-source-video-id="{{ $video->id }}"
data-subscribed="{{ $isSubscribed ? 'true' : 'false' }}">
<i class="bi {{ $isSubscribed ? 'bi-bell-fill' : 'bi-bell' }}"></i>
<span class="subscribe-label">{{ $isSubscribed ? 'Subscribed' : 'Subscribe' }}</span>
@@ -271,7 +273,7 @@
@if ($video->isShareable())
<button class="dropdown-item"
onclick="shareCurrent('{{ $video->share_url }}', '{{ addslashes($video->title) }}', '{{ route('videos.recordShare', $video) }}', '{{ Auth::check() ? route('videos.shareEmail', $video) : '' }}')">
onclick="shareCurrent('{{ $video->share_url }}', '{{ addslashes($video->title) }}', '{{ route('videos.recordShare', $video) }}', '{{ Auth::check() ? route('videos.shareEmail', $video) : '' }}', '{{ Auth::check() ? route('videos.shareMembers', $video) : '' }}')">
<i class="bi bi-share"></i> Share
</button>
@endif
@@ -337,9 +339,15 @@
el.textContent = Number(el.dataset.count).toLocaleString() + ' subscribers';
});
var srcVid = btn.dataset.sourceVideoId || '';
var body = srcVid && nowSub ? new URLSearchParams({ source_video_id: srcVid }).toString() : null;
fetch(url, {
method: 'POST',
headers: { 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json' },
headers: Object.assign(
{ 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json' },
body ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {}
),
body: body,
credentials: 'same-origin',
})
.then(function (r) {
@@ -516,10 +524,10 @@ if (!window._slideshowDlInit) {
// Share the version the viewer is on: append ?track={id} so the recipient's player
// opens directly on that language (window._ytpTrackId; 0 = primary → plain link).
window.shareCurrent = function (baseUrl, title, recordUrl, emailUrl) {
window.shareCurrent = function (baseUrl, title, recordUrl, emailUrl, membersUrl) {
var t = window._ytpTrackId || 0;
var url = t ? baseUrl + (baseUrl.indexOf('?') === -1 ? '?' : '&') + 'track=' + t : baseUrl;
if (typeof window.openShareModal === 'function') window.openShareModal(url, title, recordUrl, emailUrl);
if (typeof window.openShareModal === 'function') window.openShareModal(url, title, recordUrl, emailUrl, membersUrl);
};
window.startSlideshowDownload = function (routeKey) {