diff --git a/web/dashboard/templates/dashboard/bountyhub_programs.html b/web/dashboard/templates/dashboard/bountyhub_programs.html index 42fc451a..97d42dcf 100644 --- a/web/dashboard/templates/dashboard/bountyhub_programs.html +++ b/web/dashboard/templates/dashboard/bountyhub_programs.html @@ -60,60 +60,13 @@
-
-
-
- - -
- -
-
- Grammarly -
-
Grammarly
- @grammarly -
-
- -
- Open for Submission - Public Program - Bounty $$$ - Open Scope -
- -
-
My Reports: 0
-
My Earnings: $0.00
-
- -
- -
-
Since Sep 2017
-
USD
-
-
- - -
-
- - + {% endblock main_content %} {% block page_level_script %} - + {% endblock page_level_script %} diff --git a/web/static/custom/bountyhub.js b/web/static/custom/bountyhub.js new file mode 100644 index 00000000..eee34109 --- /dev/null +++ b/web/static/custom/bountyhub.js @@ -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 = ` +
+ +
+
+ ${attributes.name} +
+
${attributes.name}  + ${attributes.bookmarked ? '' : ''} +
+ @${attributes.handle} +
+
+ +
+ ${attributes.submission_state === 'open' ? 'Open for Submission' : 'Closed'} + ${attributes.state === 'public_mode' ? 'Public Program' : 'Private Program'} + ${attributes.offers_bounties ? 'Bounty $$$' : ''} + ${attributes.open_scope ? 'Open Scope' : ''} +
+ +
+
My Reports: ${attributes.number_of_reports_for_user}
+
My Earnings: $${attributes.bounty_earned_for_user.toFixed(2)}
+
+ +
+ +
+
Since ${new Date(attributes.started_accepting_at).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
+
${attributes.currency.toUpperCase()}
+
+ See details +
+
+ `; + + // 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 = ` +
+ +
+ `; + } + + // 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; + +}); \ No newline at end of file diff --git a/web/static/custom/custom.css b/web/static/custom/custom.css index 485ecec0..79632992 100644 --- a/web/static/custom/custom.css +++ b/web/static/custom/custom.css @@ -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; } \ No newline at end of file