Added clickable cards, added import button on any card selection, fix card designs

This commit is contained in:
Yogesh Ojha
2024-09-03 06:22:56 +05:30
parent 095fed5efc
commit 363043fa58
3 changed files with 228 additions and 51 deletions
@@ -60,60 +60,13 @@
</div>
<div class="row" id="program_cards">
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card h-100 shadow-sm position-relative overflow-hidden bbp-card">
<div class="position-absolute top-0 end-0 p-2 bbp-bookmark-container">
<input type="checkbox" id="bookmark-22746" class="bbp-bookmark-checkbox d-none" checked>
<label for="bookmark-22746" class="bbp-bookmark-label" data-bs-toggle="tooltip" data-bs-placement="left"
title="Bookmarked Program">
<i class="fe-star fs-4"></i>
</label>
</div>
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<img
src="https://profile-photos.hackerone-user-content.com/variants/gx1qb7440tyefirbxv1tkl1ty186/a11a5f547ea25bb14fce3951b07a50f6288859555c2028feb0c3aad3e1aea36d"
alt="Grammarly" class="rounded-circle me-3" width="48" height="48">
<div>
<h5 class="card-title mb-0">Grammarly</h5>
<small class="text-muted">@grammarly</small>
</div>
</div>
<div class="mb-3">
<span class="badge border border-success text-success me-1 mt-1 bbp-badge">Open for Submission</span>
<span class="badge border border-primary text-primary me-1 mt-1 bbp-badge">Public Program</span>
<span class="badge border border-info text-info me-1 mt-1 bbp-badge">Bounty $$$</span>
<span class="badge border border-warning text-warning mt-1 bbp-badge">Open Scope</span>
</div>
<div class="d-flex justify-content-between mb-3 small">
<div><i class="fe-file-text me-1"></i> My Reports: 0</div>
<div><i class="fe-dollar-sign me-1"></i> My Earnings: $0.00</div>
</div>
<hr>
<div class="d-flex justify-content-between text-muted small mb-3">
<div><i class="fe-calendar me-1"></i> Since Sep 2017</div>
<div><i class="fe-globe me-1"></i> USD</div>
</div>
</div>
<div class="card-footer bg-transparent border-0 pt-0">
<a href="#" class="btn btn-outline-primary w-100">See details</a>
</div>
</div>
</div>
</div>
<button id="importProgramsBtn" class="import-programs-btn" disabled>
<i class="fe-download-cloud"></i> Import Programs
</button>
{% endblock main_content %}
{% block page_level_script %}
<script type="text/javascript">
</script>
<script src="{% static 'custom/bountyhub.js' %}"></script>
{% endblock page_level_script %}
+131
View File
@@ -0,0 +1,131 @@
// this js file will be used to do everything js related to the bountyhub page
document.addEventListener('DOMContentLoaded', function() {
const api_url = '/api/hackerone-programs/';
Swal.fire({
title: "Loading HackerOne Programs",
text: "Fetching the latest data...",
icon: "info",
allowOutsideClick: false,
allowEscapeKey: false,
showConfirmButton: false,
didOpen: () => {
Swal.showLoading();
}
});
fetch(api_url, {
method: "GET",
credentials: "same-origin",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
Swal.close();
displayPrograms(data);
})
.catch(error => {
Swal.close();
displayErrorMessage("An error occurred while fetching the data. Please try again later. Make sure you have hackerone api key set in your API Vault.");
console.error('Error:', error);
});
function displayPrograms(programs) {
const container = document.getElementById('program_cards');
container.innerHTML = '';
if (!programs || programs.length === 0) {
displayErrorMessage("No programs available at the moment.");
return;
}
programs.forEach(program => {
const { id, attributes } = program;
const card = document.createElement('div');
card.className = 'col-md-6 col-lg-4 col-xl-3 mb-3';
card.innerHTML = `
<div class="card h-100 shadow-sm position-relative overflow-hidden bbp-card card-selectable" onclick="toggleCardSelection(this)">
<div class="card-body py-2 px-3">
<div class="d-flex align-items-center mb-2">
<img src="${attributes.profile_picture}" alt="${attributes.name}" class="rounded-circle me-3" width="40" height="40">
<div>
<h5 class="card-title mb-0">${attributes.name}&nbsp;
${attributes.bookmarked ? '<i class="text-warning mdi mdi-bookmark-check" data-bs-toggle="tooltip" data-bs-placement="top" title="Bookmarked Program"></i>' : ''}
</h5>
<small class="text-muted">@${attributes.handle}</small>
</div>
</div>
<div class="mb-2">
<span class="badge bg-success bg-opacity-10 text-success me-1 mb-1">${attributes.submission_state === 'open' ? 'Open for Submission' : 'Closed'}</span>
<span class="badge bg-primary bg-opacity-10 text-primary me-1 mb-1">${attributes.state === 'public_mode' ? 'Public Program' : 'Private Program'}</span>
${attributes.offers_bounties ? '<span class="badge bg-info bg-opacity-10 text-info me-1 mb-1">Bounty $$$</span>' : ''}
${attributes.open_scope ? '<span class="badge bg-warning bg-opacity-10 text-warning mb-1">Open Scope</span>' : ''}
</div>
<div class="d-flex justify-content-between mb-2 small">
<div><i class="bi bi-flag me-1"></i> My Reports: ${attributes.number_of_reports_for_user}</div>
<div><i class="bi bi-currency-dollar me-1"></i> My Earnings: $${attributes.bounty_earned_for_user.toFixed(2)}</div>
</div>
<hr class="my-2">
<div class="d-flex justify-content-between text-muted small mb-2">
<div><i class="bi bi-calendar me-1"></i> Since ${new Date(attributes.started_accepting_at).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}</div>
<div><i class="bi bi-globe me-1"></i> ${attributes.currency.toUpperCase()}</div>
</div>
<a href="#" class="btn btn-outline-primary btn-sm w-100 mt-2">See details</a>
</div>
</div>
`;
// Initialize tooltips esp for bookmarked programs
const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltips.forEach((tooltip) => {
new bootstrap.Tooltip(tooltip);
});
container.appendChild(card);
});
}
function displayErrorMessage(message) {
const container = document.getElementById('program_cards');
container.innerHTML = `
<div class="col-12">
<div class="alert alert-danger" role="alert">
<i class="fe-alert-triangle me-2"></i>
${message}
</div>
</div>
`;
}
// below has everything to do with card selection and import button
function toggleCardSelection(card) {
card.classList.toggle('card-selected');
updateImportButton();
}
// lets create func and listeners if cards are selected
const importBtn = document.getElementById('importProgramsBtn');
const container = document.getElementById('program_cards');
function updateImportButton() {
const selectedCards = container.querySelectorAll('.card-selected');
importBtn.disabled = selectedCards.length === 0;
}
window.toggleCardSelection = toggleCardSelection;
});
+93
View File
@@ -609,4 +609,97 @@ mark{
}
.bbp-badge:hover {
transform: translateY(-2px);
}
.card-selectable {
transition: all 0.3s ease;
cursor: pointer;
}
.card-selectable:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.card-selected {
border: 2px solid #007bff;
background-color: rgba(0,123,255,0.05);
}
.bbp-bookmark-label {
cursor: pointer;
transition: color 0.3s ease;
}
.bbp-bookmark-label:hover {
color: #ffc107;
}
.bbp-bookmark-checkbox:checked + .bbp-bookmark-label {
color: #ffc107;
}
.import-programs-btn {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
font-size: 16px;
font-weight: bold;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
opacity: 0.7;
pointer-events: none;
}
.import-programs-btn:not(:disabled) {
opacity: 1;
pointer-events: auto;
animation: enableButton 0.5s ease;
}
.import-programs-btn:hover {
transform: translateX(-50%) translateY(-5px);
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
}
.import-programs-btn:active {
transform: translateX(-50%) translateY(-2px);
box-shadow: 0 5px 7px rgba(0, 0, 0, 0.1);
}
.import-programs-btn i {
margin-right: 8px;
}
@keyframes enableButton {
0% {
transform: translateX(-50%) scale(0.9);
opacity: 0.7;
}
50% {
transform: translateX(-50%) scale(1.05);
}
100% {
transform: translateX(-50%) scale(1);
opacity: 1;
}
}
@keyframes disableButton {
0% {
transform: translateX(-50%) scale(1);
opacity: 1;
}
100% {
transform: translateX(-50%) scale(0.9);
opacity: 0.7;
}
}
.import-programs-btn:disabled {
animation: disableButton 0.3s ease forwards;
}