From bec7fac2aa47a3284358003178e727c90d1f4ae4 Mon Sep 17 00:00:00 2001 From: PawiX25 Date: Sun, 23 Mar 2025 20:22:16 +0100 Subject: [PATCH] Add program completion tracking with localStorage --- index.html | 10 ++++ script.js | 122 +++++++++++++++++++++++++++++++++++++++-- styles.css | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 284 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 66e08df..353892d 100644 --- a/index.html +++ b/index.html @@ -251,6 +251,10 @@ diff --git a/script.js b/script.js index 4f06579..52df11c 100644 --- a/script.js +++ b/script.js @@ -2,8 +2,78 @@ let programs = {}; const apiUrl = "https://api2.hackclub.com/v0.1/Unified%20YSWS%20Projects%20DB/YSWS%20Programs?cache=true"; let participants = []; let initialParticipants = new Map(); +let completedPrograms = new Set(); + +function loadCompletedPrograms() { + const saved = localStorage.getItem('completedPrograms'); + if (saved) { + completedPrograms = new Set(JSON.parse(saved)); + } +} + +function saveCompletedPrograms() { + localStorage.setItem('completedPrograms', JSON.stringify([...completedPrograms])); +} + +function toggleProgramCompletion(programName, event) { + if (event) { + event.stopPropagation(); + } + + if (completedPrograms.has(programName)) { + completedPrograms.delete(programName); + } else { + completedPrograms.add(programName); + } + + saveCompletedPrograms(); + updateCompletionUI(programName); +} + +function updateCompletionUI(programName) { + const isCompleted = completedPrograms.has(programName); + + document.querySelectorAll(`.program-card[data-name="${programName}"]`).forEach(card => { + const completionBtn = card.querySelector('.program-completion-toggle'); + const completionBadge = card.querySelector('.user-completed-badge'); + + if (completionBtn) { + completionBtn.innerHTML = isCompleted ? + '' : + ''; + + completionBtn.setAttribute('aria-label', isCompleted ? 'Mark as not completed' : 'Mark as completed'); + completionBtn.classList.toggle('completed', isCompleted); + } + + if (completionBadge) { + completionBadge.classList.toggle('visible', isCompleted); + } + }); + + const modal = document.getElementById('program-modal'); + if (modal.classList.contains('active')) { + const modalTitle = modal.querySelector('.title').textContent; + if (modalTitle === programName) { + const modalCompletionBtn = modal.querySelector('.modal-completion-toggle'); + if (modalCompletionBtn) { + modalCompletionBtn.innerHTML = isCompleted ? + ' Completed' : + ' Mark as completed'; + + modalCompletionBtn.classList.toggle('completed', isCompleted); + } + + const modalCompletionBadge = modal.querySelector('.modal-completion-badge'); + if (modalCompletionBadge) { + modalCompletionBadge.classList.toggle('visible', isCompleted); + } + } + } +} async function startRender() { + loadCompletedPrograms(); await loadPrograms(); Object.values(programs).flat().forEach(program => { if (program.participants !== undefined) { @@ -229,21 +299,38 @@ function createProgramCard(program) { const encodedProgram = encodeURIComponent(JSON.stringify(program)); + const isCompletedByUser = completedPrograms.has(program.name); + const completionButtonClass = isCompletedByUser ? 'completed' : ''; + const completionIcon = isCompletedByUser ? + '' : + ''; + const participantsText = program.participants !== undefined ? `
${formatParticipants(program.name)}
` : ''; return ` -
+

${program.name}

- ${program.status} +
+ + + You completed this + + ${program.status} +

${program.description}

${deadlineText}
${participantsText} - `; @@ -343,6 +430,17 @@ function openModal(program) { if (program.website) links.push(`Website`); if (program.slack) links.push(`${program.slackChannel}`); modal.querySelector('.program-links').innerHTML = links.join(' | '); + + const isCompletedByUser = completedPrograms.has(program.name); + const modalCompletionBtn = modal.querySelector('.modal-completion-toggle'); + modalCompletionBtn.innerHTML = isCompletedByUser ? + ' Completed' : + ' Mark as completed'; + modalCompletionBtn.classList.toggle('completed', isCompletedByUser); + modalCompletionBtn.dataset.programName = program.name; + + const modalCompletionBadge = modal.querySelector('.modal-completion-badge'); + modalCompletionBadge.classList.toggle('visible', isCompletedByUser); updatePositionIndicator(); modal.classList.add('active'); @@ -570,6 +668,20 @@ document.addEventListener('DOMContentLoaded', () => { }); document.addEventListener('click', (e) => { + if (e.target.closest('.program-completion-toggle')) { + const button = e.target.closest('.program-completion-toggle'); + const programName = button.dataset.programName; + toggleProgramCompletion(programName, e); + return; + } + + if (e.target.closest('.modal-completion-toggle')) { + const button = e.target.closest('.modal-completion-toggle'); + const programName = button.dataset.programName; + toggleProgramCompletion(programName, e); + return; + } + if (e.target.closest('.program-card') && e.target.closest('a')) { return; } diff --git a/styles.css b/styles.css index 0ca6514..0281577 100644 --- a/styles.css +++ b/styles.css @@ -1944,4 +1944,161 @@ html { .rss-link:hover svg { transform: scale(1.2); +} + +.program-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: var(--spacing-3); +} + +.program-completion-toggle { + border: none; + background: rgba(255, 255, 255, 0.2); + color: var(--text); + width: 36px; + height: 36px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: none; + padding: 0; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + margin-left: var(--spacing-2); + backdrop-filter: blur(5px); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.dark-theme .program-completion-toggle { + background: rgba(23, 23, 29, 0.5); + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.program-completion-toggle:hover { + transform: scale(1.1); + background: rgba(51, 214, 166, 0.2); + box-shadow: 0 0 10px rgba(51, 214, 166, 0.3); +} + +.program-completion-toggle.completed { + background: var(--green); + color: white; +} + +.program-completion-toggle.completed:hover { + background: var(--red); +} + +.user-completed-badge { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: var(--font-1); + font-weight: var(--font-weight-bold); + color: var(--green); + background: rgba(51, 214, 166, 0.1); + border: 1px solid var(--green); + padding: var(--spacing-1) var(--spacing-2); + border-radius: var(--radii-circle); + margin-right: var(--spacing-2); + opacity: 0; + transform: translateY(10px); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + pointer-events: none; + visibility: hidden; +} + +.user-completed-badge.visible { + opacity: 1; + transform: translateY(0); + visibility: visible; +} + +.status-container { + display: flex; + align-items: center; + flex-wrap: nowrap; +} + +.modal-completion-toggle { + display: flex; + align-items: center; + gap: 8px; + font-size: var(--font-2); + font-weight: var(--font-weight-bold); + background: rgba(255, 255, 255, 0.1); + color: var(--text); + padding: var(--spacing-2) var(--spacing-3); + border-radius: var(--radii-circle); + border: 1px solid rgba(255, 255, 255, 0.1); + margin-top: var(--spacing-3); + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + backdrop-filter: blur(5px); +} + +.dark-theme .modal-completion-toggle { + background: rgba(23, 23, 29, 0.5); + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.modal-completion-toggle:hover { + transform: translateY(-2px); + background: rgba(51, 214, 166, 0.2); + box-shadow: 0 4px 12px rgba(51, 214, 166, 0.2); +} + +.modal-completion-toggle.completed { + background: var(--green); + color: white; +} + +.modal-completion-toggle.completed:hover { + background: var(--red); +} + +.modal-completion-badge { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: var(--font-2); + font-weight: var(--font-weight-bold); + color: var(--green); + background: rgba(51, 214, 166, 0.1); + border: 1px solid var(--green); + padding: var(--spacing-2) var(--spacing-3); + border-radius: var(--radii-circle); + margin-bottom: var(--spacing-3); + opacity: 0; + transform: translateY(10px); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + pointer-events: none; + visibility: hidden; +} + +.modal-completion-badge.visible { + opacity: 1; + transform: translateY(0); + visibility: visible; +} + +.modal-completion-container { + display: flex; + flex-direction: column; + margin-top: var(--spacing-3); +} + +@media (max-width: 768px) { + .status-container { + flex-direction: column; + align-items: flex-end; + } + + .user-completed-badge { + margin-bottom: var(--spacing-1); + margin-right: 0; + } } \ No newline at end of file