Add release reporting feature

This commit is contained in:
DariusIII
2026-02-01 22:16:40 +01:00
parent 239266fa04
commit b60d8b29e3
18 changed files with 1261 additions and 6 deletions
+290
View File
@@ -5727,3 +5727,293 @@ function initSortDropdowns() {
// Initialize sort dropdowns on DOMContentLoaded
document.addEventListener('DOMContentLoaded', initSortDropdowns);
// =============================================================================
// RELEASE REPORT FUNCTIONALITY
// =============================================================================
/**
* Initialize release report buttons and modals
* CSP-safe implementation for reporting bad releases
*/
function initReleaseReportButtons() {
// Find all report button containers
var containers = document.querySelectorAll('.report-button-container');
containers.forEach(function(container) {
var trigger = container.querySelector('.report-trigger');
var modal = container.querySelector('.report-modal');
if (!trigger || !modal) return;
// Skip if already initialized
if (trigger.hasAttribute('data-report-initialized')) return;
trigger.setAttribute('data-report-initialized', 'true');
var releaseId = trigger.getAttribute('data-report-release-id');
var form = modal.querySelector('.report-form');
var closeButtons = modal.querySelectorAll('.report-modal-close');
var backdrop = modal.querySelector('.report-modal-backdrop');
var reasonSelect = modal.querySelector('.report-reason');
var descriptionTextarea = modal.querySelector('.report-description');
var charCount = modal.querySelector('.report-char-count');
var submitButton = modal.querySelector('.report-submit');
var errorDiv = modal.querySelector('.report-error');
var successDiv = modal.querySelector('.report-success');
var reportLabel = trigger.querySelector('.report-label');
var flagIcon = trigger.querySelector('i');
var hasReported = false;
var isSubmitting = false;
// Open modal
trigger.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (hasReported) return;
modal.classList.remove('hidden');
// Reset form state
if (errorDiv) errorDiv.classList.add('hidden');
if (successDiv) successDiv.classList.add('hidden');
if (reasonSelect) reasonSelect.value = '';
if (descriptionTextarea) descriptionTextarea.value = '';
if (charCount) charCount.textContent = '0/1000 characters';
if (submitButton) submitButton.disabled = true;
});
// Close modal function
function closeModal() {
modal.classList.add('hidden');
if (hasReported) {
trigger.disabled = true;
trigger.classList.add('opacity-50', 'cursor-not-allowed');
if (flagIcon) flagIcon.classList.add('text-red-500');
if (reportLabel) reportLabel.textContent = 'Reported';
}
}
// Close button handlers
closeButtons.forEach(function(closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.preventDefault();
closeModal();
});
});
// Backdrop click to close
if (backdrop) {
backdrop.addEventListener('click', function() {
closeModal();
});
}
// Character count for description
if (descriptionTextarea && charCount) {
descriptionTextarea.addEventListener('input', function() {
charCount.textContent = this.value.length + '/1000 characters';
});
}
// Enable/disable submit based on reason selection
if (reasonSelect && submitButton) {
reasonSelect.addEventListener('change', function() {
submitButton.disabled = !this.value;
});
}
// Submit button click handler
if (submitButton) {
submitButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (isSubmitting || !reasonSelect || !reasonSelect.value) return;
isSubmitting = true;
submitButton.disabled = true;
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Submitting...';
if (errorDiv) errorDiv.classList.add('hidden');
if (successDiv) successDiv.classList.add('hidden');
var csrfToken = document.querySelector('meta[name="csrf-token"]');
var csrfValue = csrfToken ? csrfToken.getAttribute('content') : '';
var requestBody = {
release_id: releaseId,
reason: reasonSelect.value,
description: descriptionTextarea ? descriptionTextarea.value : ''
};
fetch('/release-report', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfValue,
'Accept': 'application/json'
},
body: JSON.stringify(requestBody)
})
.then(function(response) {
return response.json().then(function(data) {
return { ok: response.ok, data: data };
});
})
.then(function(result) {
if (result.ok && result.data.success) {
if (successDiv) {
successDiv.querySelector('p').textContent = result.data.message;
successDiv.classList.remove('hidden');
}
hasReported = true;
setTimeout(closeModal, 2000);
} else {
if (errorDiv) {
errorDiv.querySelector('p').textContent = result.data.message || 'An error occurred. Please try again.';
errorDiv.classList.remove('hidden');
}
}
})
.catch(function(error) {
if (errorDiv) {
errorDiv.querySelector('p').textContent = 'Network error. Please try again.';
errorDiv.classList.remove('hidden');
}
})
.finally(function() {
isSubmitting = false;
if (submitButton) {
submitButton.disabled = !reasonSelect.value;
submitButton.innerHTML = 'Submit Report';
}
});
});
}
});
}
// Initialize report buttons on DOMContentLoaded
document.addEventListener('DOMContentLoaded', initReleaseReportButtons);
// Re-initialize on dynamic content load (for AJAX-loaded content)
document.addEventListener('contentLoaded', initReleaseReportButtons);
// =============================================================================
// ADMIN RELEASE REPORTS PAGE FUNCTIONALITY
// =============================================================================
/**
* Initialize admin release reports page functionality
* Handles description modal, select all, and bulk actions
*/
function initAdminReleaseReports() {
// Report Description Modal
var descModal = document.getElementById('reportDescriptionModal');
var descButtons = document.querySelectorAll('.report-description-btn');
var descContent = document.getElementById('reportDescContent');
var descReason = document.getElementById('reportDescReason');
var descReporter = document.getElementById('reportDescReporter');
var descCloseButtons = document.querySelectorAll('.report-desc-modal-close');
var descBackdrop = document.querySelector('.report-desc-modal-backdrop');
// Open description modal
descButtons.forEach(function(btn) {
if (btn.hasAttribute('data-desc-initialized')) return;
btn.setAttribute('data-desc-initialized', 'true');
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
var description = this.getAttribute('data-description');
var reason = this.getAttribute('data-reason');
var reporter = this.getAttribute('data-reporter');
if (descContent) descContent.textContent = description || 'No additional details provided.';
if (descReason) descReason.textContent = reason || 'Unknown';
if (descReporter) descReporter.textContent = reporter || 'Unknown';
if (descModal) descModal.classList.remove('hidden');
});
});
// Close description modal
function closeDescModal() {
if (descModal) descModal.classList.add('hidden');
}
descCloseButtons.forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.preventDefault();
closeDescModal();
});
});
if (descBackdrop) {
descBackdrop.addEventListener('click', closeDescModal);
}
// Select All functionality
var selectAll = document.getElementById('select-all');
var checkboxes = document.querySelectorAll('.report-checkbox');
if (selectAll && !selectAll.hasAttribute('data-selectall-initialized')) {
selectAll.setAttribute('data-selectall-initialized', 'true');
selectAll.addEventListener('change', function() {
checkboxes.forEach(function(checkbox) {
checkbox.checked = selectAll.checked;
});
});
}
// Update select all checkbox when individual checkboxes change
checkboxes.forEach(function(checkbox) {
if (checkbox.hasAttribute('data-checkbox-initialized')) return;
checkbox.setAttribute('data-checkbox-initialized', 'true');
checkbox.addEventListener('change', function() {
var allChecked = Array.from(checkboxes).every(function(cb) { return cb.checked; });
var someChecked = Array.from(checkboxes).some(function(cb) { return cb.checked; });
if (selectAll) {
selectAll.checked = allChecked;
selectAll.indeterminate = someChecked && !allChecked;
}
});
});
// Bulk action form validation
var bulkForm = document.getElementById('bulk-action-form');
if (bulkForm && !bulkForm.hasAttribute('data-bulk-initialized')) {
bulkForm.setAttribute('data-bulk-initialized', 'true');
bulkForm.addEventListener('submit', function(e) {
var actionSelect = this.querySelector('select[name="action"]');
var action = actionSelect ? actionSelect.value : '';
var checkedCount = document.querySelectorAll('.report-checkbox:checked').length;
if (!action) {
e.preventDefault();
alert('Please select an action.');
return;
}
if (checkedCount === 0) {
e.preventDefault();
alert('Please select at least one report.');
return;
}
if (action === 'delete') {
if (!confirm('Are you sure you want to DELETE ' + checkedCount + ' release(s)? This action cannot be undone.')) {
e.preventDefault();
}
}
});
}
}
// Initialize admin release reports on DOMContentLoaded
document.addEventListener('DOMContentLoaded', initAdminReleaseReports);
@@ -0,0 +1,323 @@
@extends('layouts.admin')
@section('content')
<div class="container mx-auto px-4 py-6">
<!-- Flash Messages -->
@if(session('success'))
<div class="mb-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4 flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-check-circle text-green-600 dark:text-green-400 mr-3 text-xl"></i>
<p class="text-sm text-green-800 dark:text-green-200 font-medium">{{ session('success') }}</p>
</div>
<button onclick="this.parentElement.parentElement.remove()" class="text-green-600 dark:text-green-400 hover:text-green-800 dark:hover:text-green-200">
<i class="fas fa-times"></i>
</button>
</div>
@endif
@if(session('error'))
<div class="mb-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-exclamation-circle text-red-600 dark:text-red-400 mr-3 text-xl"></i>
<p class="text-sm text-red-800 dark:text-red-200 font-medium">{{ session('error') }}</p>
</div>
<button onclick="this.parentElement.parentElement.remove()" class="text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-200">
<i class="fas fa-times"></i>
</button>
</div>
@endif
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm">
<!-- Header -->
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<div>
<h1 class="text-2xl font-semibold text-gray-800 dark:text-gray-200">
<i class="fas fa-flag mr-2 text-red-500"></i>{{ $title }}
</h1>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">Manage release reports submitted by users</p>
</div>
<!-- Status Stats -->
<div class="flex flex-wrap gap-2">
<a href="{{ route('admin.release-reports', ['status' => 'pending']) }}"
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'pending' ? 'bg-yellow-500 text-white' : 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 hover:bg-yellow-200 dark:hover:bg-yellow-800' }}">
<i class="fas fa-clock mr-1"></i> Pending ({{ $statusCounts['pending'] }})
</a>
<a href="{{ route('admin.release-reports', ['status' => 'reviewed']) }}"
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'reviewed' ? 'bg-blue-500 text-white' : 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 hover:bg-blue-200 dark:hover:bg-blue-800' }}">
<i class="fas fa-eye mr-1"></i> Reviewed ({{ $statusCounts['reviewed'] }})
</a>
<a href="{{ route('admin.release-reports', ['status' => 'resolved']) }}"
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'resolved' ? 'bg-green-500 text-white' : 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 hover:bg-green-200 dark:hover:bg-green-800' }}">
<i class="fas fa-check mr-1"></i> Resolved ({{ $statusCounts['resolved'] }})
</a>
<a href="{{ route('admin.release-reports', ['status' => 'dismissed']) }}"
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'dismissed' ? 'bg-gray-500 text-white' : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600' }}">
<i class="fas fa-ban mr-1"></i> Dismissed ({{ $statusCounts['dismissed'] }})
</a>
<a href="{{ route('admin.release-reports', ['status' => 'all']) }}"
class="px-3 py-1.5 rounded-full text-sm font-medium transition {{ $status === 'all' ? 'bg-purple-500 text-white' : 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 hover:bg-purple-200 dark:hover:bg-purple-800' }}">
<i class="fas fa-list mr-1"></i> All ({{ $statusCounts['total'] }})
</a>
</div>
</div>
</div>
<!-- Reports Table -->
@if($reportsList->count() > 0)
<form id="bulk-action-form" method="POST" action="{{ route('admin.release-reports.bulk') }}">
@csrf
<div class="px-6 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 flex items-center gap-4">
<input type="checkbox" id="select-all" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-700">
<select name="action" class="text-sm border-gray-300 dark:border-gray-600 rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-200">
<option value="">Bulk Actions</option>
<option value="reviewed">Mark as Reviewed</option>
<option value="resolve">Mark as Resolved</option>
<option value="dismiss">Dismiss Selected</option>
<option value="delete">Delete Releases & Resolve</option>
</select>
<button type="submit" class="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition">
Apply
</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-900">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-10"></th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">ID</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Release</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Reporter</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Reason</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
@foreach($reportsList as $report)
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
<td class="px-4 py-4 whitespace-nowrap">
<input type="checkbox" name="report_ids[]" value="{{ $report->id }}" class="report-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500 dark:bg-gray-700">
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-200 font-mono">
#{{ $report->id }}
</td>
<td class="px-4 py-4">
<div class="max-w-md">
@if($report->release)
<a href="{{ url('/details/' . $report->release->guid) }}"
target="_blank"
class="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 font-medium text-sm break-all">
{{ Str::limit($report->release->searchname, 80) }}
</a>
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
ID: {{ $report->releases_id }} | Size: {{ number_format($report->release->size / 1073741824, 2) }} GB
</div>
@else
<span class="text-red-500 dark:text-red-400 text-sm">
<i class="fas fa-exclamation-triangle mr-1"></i> Release Deleted
</span>
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
Original ID: {{ $report->releases_id }}
</div>
@endif
</div>
</td>
<td class="px-4 py-4 whitespace-nowrap">
<div class="text-sm text-gray-900 dark:text-gray-200">
@if($report->user)
{{ $report->user->username }}
@else
<span class="text-gray-500">Deleted User</span>
@endif
</div>
</td>
<td class="px-4 py-4">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200">
{{ $report->reason_label }}
</span>
@if($report->description)
<button type="button"
class="report-description-btn ml-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
data-description="{{ htmlspecialchars($report->description, ENT_QUOTES) }}"
data-reason="{{ $report->reason_label }}"
data-reporter="{{ $report->user ? $report->user->username : 'Unknown' }}"
title="View description">
<i class="fas fa-comment-dots"></i>
</button>
@endif
</td>
<td class="px-4 py-4 whitespace-nowrap">
@php
$statusColors = [
'pending' => 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200',
'reviewed' => 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200',
'resolved' => 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200',
'dismissed' => 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200',
];
$statusIcons = [
'pending' => 'fa-clock',
'reviewed' => 'fa-eye',
'resolved' => 'fa-check',
'dismissed' => 'fa-ban',
];
@endphp
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $statusColors[$report->status] ?? 'bg-gray-100 text-gray-800' }}">
<i class="fas {{ $statusIcons[$report->status] ?? 'fa-question' }} mr-1"></i>
{{ ucfirst($report->status) }}
</span>
@if($report->reviewer && $report->reviewed_at)
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
by {{ $report->reviewer->username }} at {{ $report->reviewed_at->format('M d, Y H:i') }}
</div>
@endif
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
{{ $report->created_at->format('M d, Y') }}
<div class="text-xs">{{ $report->created_at->format('H:i') }}</div>
</td>
<td class="px-4 py-4 whitespace-nowrap">
<div class="flex items-center gap-2 flex-wrap">
@if($report->release && in_array($report->status, ['pending', 'reviewed']))
<!-- Delete Release Button -->
<form method="POST" action="{{ route('admin.release-reports.delete-release', $report->id) }}" class="inline" onsubmit="return confirm('Are you sure you want to DELETE this release? This action cannot be undone.');">
@csrf
<button type="submit"
class="px-3 py-1.5 bg-red-600 text-white rounded-lg hover:bg-red-700 transition text-sm inline-flex items-center"
title="Delete Release">
<i class="fas fa-trash mr-1"></i> Delete
</button>
</form>
@endif
@if($report->status === 'pending')
<!-- Mark as Reviewed Button -->
<form method="POST" action="{{ route('admin.release-reports.update-status', $report->id) }}" class="inline">
@csrf
<input type="hidden" name="status" value="reviewed">
<button type="submit"
class="px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-sm inline-flex items-center"
title="Mark as Reviewed">
<i class="fas fa-eye mr-1"></i> Reviewed
</button>
</form>
<!-- Dismiss Button -->
<form method="POST" action="{{ route('admin.release-reports.dismiss', $report->id) }}" class="inline">
@csrf
<button type="submit"
class="px-3 py-1.5 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition text-sm inline-flex items-center"
title="Dismiss Report">
<i class="fas fa-ban mr-1"></i> Dismiss
</button>
</form>
@elseif($report->status === 'reviewed')
<!-- Mark as Resolved Button -->
<form method="POST" action="{{ route('admin.release-reports.update-status', $report->id) }}" class="inline">
@csrf
<input type="hidden" name="status" value="resolved">
<button type="submit"
class="px-3 py-1.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition text-sm inline-flex items-center"
title="Mark as Resolved">
<i class="fas fa-check mr-1"></i> Resolve
</button>
</form>
<!-- Dismiss Button -->
<form method="POST" action="{{ route('admin.release-reports.dismiss', $report->id) }}" class="inline">
@csrf
<button type="submit"
class="px-3 py-1.5 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition text-sm inline-flex items-center"
title="Dismiss Report">
<i class="fas fa-ban mr-1"></i> Dismiss
</button>
</form>
@elseif(!$report->release)
<span class="text-xs text-gray-500 dark:text-gray-400">Release already deleted</span>
@else
<span class="text-xs text-gray-500 dark:text-gray-400">{{ ucfirst($report->status) }}</span>
@endif
@if($report->release)
<a href="{{ url('/details/' . $report->release->guid) }}"
target="_blank"
class="px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-sm inline-flex items-center"
title="View Release">
<i class="fas fa-external-link-alt"></i>
</a>
@endif
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</form>
<!-- Pagination -->
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700">
{{ $reportsList->appends(['status' => $status])->links() }}
</div>
@else
<div class="px-6 py-12 text-center">
<i class="fas fa-flag text-gray-300 dark:text-gray-600 text-6xl mb-4"></i>
<h3 class="text-xl font-medium text-gray-700 dark:text-gray-300 mb-2">No Reports Found</h3>
<p class="text-gray-500 dark:text-gray-400">
@if($status !== 'all')
No {{ $status }} reports at this time.
<a href="{{ route('admin.release-reports', ['status' => 'all']) }}" class="text-blue-600 dark:text-blue-400 hover:underline">View all reports</a>
@else
No release reports have been submitted yet.
@endif
</p>
</div>
@endif
</div>
</div>
<!-- Report Description Modal -->
<div id="reportDescriptionModal" class="fixed inset-0 z-50 overflow-y-auto hidden">
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
<!-- Backdrop -->
<div class="report-desc-modal-backdrop fixed inset-0 transition-opacity bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-75"></div>
<!-- Modal Content -->
<div class="relative inline-block w-full max-w-lg p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 shadow-xl rounded-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
<i class="fas fa-comment-dots text-blue-500 mr-2"></i>Report Details
</h3>
<button type="button" class="report-desc-modal-close text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Reason</label>
<p id="reportDescReason" class="text-gray-900 dark:text-gray-100 font-medium"></p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Reported By</label>
<p id="reportDescReporter" class="text-gray-900 dark:text-gray-100"></p>
</div>
<div>
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Additional Details</label>
<div id="reportDescContent" class="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg text-gray-700 dark:text-gray-300 whitespace-pre-wrap break-words max-h-64 overflow-y-auto"></div>
</div>
</div>
<div class="mt-6 flex justify-end">
<button type="button" class="report-desc-modal-close px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition">
Close
</button>
</div>
</div>
</div>
</div>
@endsection
+7
View File
@@ -141,6 +141,12 @@
<div class="flex-1">
<div class="flex items-center gap-2 flex-wrap">
<a href="{{ url('/details/' . $result->guid) }}" class="text-blue-600 dark:text-blue-400 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 dark:hover:text-blue-300 font-medium break-words break-all">{{ $result->searchname }}</a>
@if(!empty($result->report_count) && $result->report_count > 0)
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200"
title="Reported: {{ \App\Models\ReleaseReport::reasonKeysToLabels($result->report_reasons ?? '') }}">
<i class="fas fa-flag mr-1"></i> Reported ({{ $result->report_count }})
</span>
@endif
@if(!empty($result->failed_count) && $result->failed_count > 0)
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
title="{{ $result->failed_count }} user(s) reported download failure">
@@ -254,6 +260,7 @@
<i class="fa fa-film"></i>
</a>
@endif
<x-report-button :release-id="$result->id" variant="icon" />
</div>
</td>
</tr>
@@ -0,0 +1,113 @@
@props([
'releaseId' => null,
'releaseGuid' => null,
'size' => 'sm', // sm, md, lg
'variant' => 'icon', // icon, button, text
])
@auth
<div class="report-button-container inline-block">
<!-- Report Button -->
@if($variant === 'icon')
<button type="button"
data-report-release-id="{{ $releaseId }}"
class="report-trigger text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 transition p-1"
title="Report this release">
<i class="fas fa-flag"></i>
</button>
@elseif($variant === 'button')
<button type="button"
data-report-release-id="{{ $releaseId }}"
class="report-trigger px-3 py-1.5 text-{{ $size }} bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-red-100 dark:hover:bg-red-900 hover:text-red-700 dark:hover:text-red-300 transition inline-flex items-center">
<i class="fas fa-flag mr-1.5"></i>
<span class="report-label">Report</span>
</button>
@else
<button type="button"
data-report-release-id="{{ $releaseId }}"
class="report-trigger text-gray-600 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 text-{{ $size }} transition">
<i class="fas fa-flag mr-1"></i>
<span class="report-label">Report Issue</span>
</button>
@endif
<!-- Report Modal -->
<div class="report-modal fixed inset-0 z-50 overflow-y-auto hidden" data-release-id="{{ $releaseId }}">
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
<!-- Backdrop -->
<div class="report-modal-backdrop fixed inset-0 transition-opacity bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-75"></div>
<!-- Modal Content -->
<div class="relative inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-gray-800 shadow-xl rounded-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
<i class="fas fa-flag text-red-500 mr-2"></i>Report Release
</h3>
<button type="button" class="report-modal-close text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<form class="report-form" data-ajax-form="true">
<!-- Reason Select -->
<div class="mb-4">
<label for="report-reason-{{ $releaseId }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Reason for Report <span class="text-red-500">*</span>
</label>
<select name="reason"
id="report-reason-{{ $releaseId }}"
required
class="report-reason w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">Select a reason...</option>
<option value="duplicate">Duplicate Release</option>
<option value="fake">Fake/Malicious Content</option>
<option value="password">Password Protected</option>
<option value="incomplete">Incomplete/Corrupted</option>
<option value="wrong_category">Wrong Category</option>
<option value="spam">Spam/Advertisement</option>
<option value="other">Other</option>
</select>
</div>
<!-- Description Textarea -->
<div class="mb-4">
<label for="report-description-{{ $releaseId }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Additional Details (optional)
</label>
<textarea name="description"
id="report-description-{{ $releaseId }}"
rows="3"
maxlength="1000"
placeholder="Provide any additional details that might help us review this report..."
class="report-description w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-red-500 focus:border-red-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 resize-none"></textarea>
<p class="report-char-count text-xs text-gray-500 dark:text-gray-400 mt-1">0/1000 characters</p>
</div>
<!-- Error/Success Messages -->
<div class="report-error hidden mb-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<p class="text-sm text-red-600 dark:text-red-400"></p>
</div>
<div class="report-success hidden mb-4 p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
<p class="text-sm text-green-600 dark:text-green-400"></p>
</div>
<!-- Action Buttons -->
<div class="flex justify-end space-x-3">
<button type="button"
class="report-modal-close px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition">
Cancel
</button>
<button type="button"
disabled
class="report-submit px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700 transition disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center">
Submit Report
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endauth
+16 -6
View File
@@ -30,12 +30,21 @@
<div class="flex-1">
<div class="mb-3">
<h2 class="text-xl font-bold text-gray-800 dark:text-gray-200 mb-2 break-words break-all">{{ $release->searchname }}</h2>
@if(!empty($failed) && $failed > 0)
<div class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium bg-red-100 text-red-800 border border-red-200">
<i class="fas fa-exclamation-triangle mr-2"></i>
<span>{{ $failed }} user{{ $failed > 1 ? 's' : '' }} reported download failure</span>
</div>
@endif
<div class="flex flex-wrap gap-2">
@if(!empty($reportCount) && $reportCount > 0)
<div class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 border border-orange-200 dark:border-orange-800"
title="Reported: {{ $reportReasons ?? 'Unknown' }}">
<i class="fas fa-flag mr-2"></i>
<span>{{ $reportCount }} report{{ $reportCount > 1 ? 's' : '' }}: {{ $reportReasons ?? 'Unknown' }}</span>
</div>
@endif
@if(!empty($failed) && $failed > 0)
<div class="inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium bg-red-100 text-red-800 border border-red-200">
<i class="fas fa-exclamation-triangle mr-2"></i>
<span>{{ $failed }} user{{ $failed > 1 ? 's' : '' }} reported download failure</span>
</div>
@endif
</div>
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ url('/getnzb/' . $release->guid) }}" class="download-nzb px-4 py-2 bg-green-600 dark:bg-green-700 text-white rounded-lg hover:bg-green-700 dark:hover:bg-green-800 transition inline-flex items-center">
@@ -49,6 +58,7 @@
<i class="fas fa-file-alt mr-2"></i> View NFO
</button>
@endif
<x-report-button :release-id="$release->id" variant="button" />
@auth
@if(auth()->user()->hasRole('Admin') || auth()->user()->hasRole('Moderator'))
<a href="{{ route('admin.release-edit', ['id' => $release->guid]) }}" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700 transition inline-flex items-center" title="Edit Release">
+23
View File
@@ -148,6 +148,24 @@
<h3 class="text-xl font-bold text-gray-900">{{ $result->title }}</h3>
@endif
@if(!empty($result->grp_release_reports))
@php
$reportCounts = array_filter(explode(',', $result->grp_release_reports));
$totalReports = array_sum($reportCounts);
$reportReasonsRaw = !empty($result->grp_release_report_reasons)
? implode(',', array_unique(array_filter(preg_split('/[|,]/', $result->grp_release_report_reasons))))
: '';
$reportReasons = \App\Models\ReleaseReport::reasonKeysToLabels($reportReasonsRaw);
@endphp
@if($totalReports > 0)
<div class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200 border border-orange-200 dark:border-orange-800 mt-1"
title="Reported: {{ $reportReasons }}">
<i class="fas fa-flag mr-1"></i>
<span>{{ $totalReports }} report{{ $totalReports > 1 ? 's' : '' }}</span>
</div>
@endif
@endif
@if(!empty($result->grp_release_failed))
@php
$failedCounts = array_filter(explode(',', $result->grp_release_failed));
@@ -227,6 +245,7 @@
$releaseNames = isset($result->grp_release_name) ? explode('#', $result->grp_release_name) : [];
$releaseSizes = isset($result->grp_release_size) ? explode(',', $result->grp_release_size) : [];
$releaseGuids = isset($result->grp_release_guid) ? explode(',', $result->grp_release_guid) : [];
$releaseIds = isset($result->grp_release_id) ? explode(',', $result->grp_release_id) : [];
$releasePostDates = isset($result->grp_release_postdate) ? explode(',', $result->grp_release_postdate) : [];
$releaseAddDates = isset($result->grp_release_adddate) ? explode(',', $result->grp_release_adddate) : [];
$releaseHasPreview = isset($result->grp_haspreview) ? explode(',', $result->grp_haspreview) : [];
@@ -240,6 +259,7 @@
$releaseNames = array_slice($releaseNames, 0, $maxReleases);
$releaseSizes = array_slice($releaseSizes, 0, $maxReleases);
$releaseGuids = array_slice($releaseGuids, 0, $maxReleases);
$releaseIds = array_slice($releaseIds, 0, $maxReleases);
$releasePostDates = array_slice($releasePostDates, 0, $maxReleases);
$releaseAddDates = array_slice($releaseAddDates, 0, $maxReleases);
$releaseHasPreview = array_slice($releaseHasPreview, 0, $maxReleases);
@@ -327,6 +347,9 @@
<a href="{{ url('/details/' . $releaseGuids[$index]) }}" class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-600 dark:bg-gray-700 text-white hover:bg-gray-700 dark:hover:bg-gray-800 transition">
<i class="fas fa-info-circle mr-1"></i> Details
</a>
@if(isset($releaseIds[$index]))
<x-report-button :release-id="(int)$releaseIds[$index]" variant="icon" />
@endif
</div>
</div>
</div>
@@ -67,6 +67,9 @@
</button>
<div id="releases-menu" class="hidden mt-2 ml-6 space-y-1">
<a href="{{ url('/admin/release-list') }}" class="block py-2 px-3 text-gray-400 dark:text-gray-500 hover:text-white dark:hover:text-white hover:bg-gray-800 dark:hover:bg-gray-800 rounded transition">Release List</a>
<a href="{{ url('/admin/release-reports') }}" class="block py-2 px-3 text-gray-400 dark:text-gray-500 hover:text-white dark:hover:text-white hover:bg-gray-800 dark:hover:bg-gray-800 rounded transition">
<i class="fas fa-flag text-red-500 mr-1"></i> Release Reports
</a>
<a href="{{ url('/admin/failrel-list') }}" class="block py-2 px-3 text-gray-400 dark:text-gray-500 hover:text-white dark:hover:text-white hover:bg-gray-800 dark:hover:bg-gray-800 rounded transition">Failed Releases</a>
<a href="{{ url('/admin/category-list') }}" class="block py-2 px-3 text-gray-400 dark:text-gray-500 hover:text-white dark:hover:text-white hover:bg-gray-800 dark:hover:bg-gray-800 rounded transition">Categories</a>
</div>
+14
View File
@@ -210,6 +210,12 @@
<div class="flex-1">
<div class="flex items-center gap-2 flex-wrap">
<a href="{{ url('/details/' . $result->guid) }}" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 font-medium break-words break-all">{{ $result->searchname }}</a>
@if(!empty($result->report_count) && $result->report_count > 0)
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200"
title="Reported: {{ \App\Models\ReleaseReport::reasonKeysToLabels($result->report_reasons ?? '') }}">
<i class="fas fa-flag mr-1"></i> Reported ({{ $result->report_count }})
</span>
@endif
@if(!empty($result->failed_count) && $result->failed_count > 0)
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
title="{{ $result->failed_count }} user(s) reported download failure">
@@ -322,6 +328,7 @@
<i class="fa fa-film"></i>
</a>
@endif
<x-report-button :release-id="$result->id" variant="icon" />
</div>
</td>
</tr>
@@ -341,6 +348,12 @@
<a href="{{ url('/details/' . $result->guid) }}" class="text-lg font-medium text-blue-600 dark:text-blue-400 hover:text-blue-800 break-words break-all">
{{ $result->searchname }}
</a>
@if(!empty($result->report_count) && $result->report_count > 0)
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200"
title="Reported: {{ \App\Models\ReleaseReport::reasonKeysToLabels($result->report_reasons ?? '') }}">
<i class="fas fa-flag mr-1"></i> Reported ({{ $result->report_count }})
</span>
@endif
@if(!empty($result->failed_count) && $result->failed_count > 0)
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
title="{{ $result->failed_count }} user(s) reported download failure">
@@ -429,6 +442,7 @@
<i class="fa fa-film"></i>
</a>
@endif
<x-report-button :release-id="$result->id" variant="icon" />
</div>
</div>
</div>