diff --git a/index.html b/index.html index 1a9d1ad..8faca07 100644 --- a/index.html +++ b/index.html @@ -931,6 +931,138 @@ animation: copyFlash 0.5s; } } + + /* Comments Section Styles */ +.comments-section { + margin-top: 15px; + border-top: 1px solid var(--border); + padding-top: 15px; +} + +.comments-toggle { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + display: flex; + align-items: center; + font-size: 0.9rem; + padding: 0; + margin-bottom: 10px; +} + +.comments-toggle:hover { + color: var(--primary); +} + +.comments-toggle-icon { + margin-right: 5px; + transition: transform 0.3s ease; +} + +.comments-toggle[aria-expanded="true"] .comments-toggle-icon { + transform: rotate(90deg); +} + +.comments-container { + display: none; + margin-top: 10px; +} + +.comments-container.show { + display: block; +} + +.comment-item { + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.comment-item:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.comment-header { + display: flex; + justify-content: space-between; + margin-bottom: 5px; +} + +.comment-author { + font-weight: 600; + font-size: 0.9rem; + color: var(--primary); +} + +.comment-time { + font-size: 0.8rem; + color: var(--text-muted); +} + +.comment-text { + font-size: 0.95rem; + line-height: 1.5; + word-wrap: break-word; +} + +.comment-form { + display: flex; + margin-top: 15px; + margin-bottom: 10px; +} + +.comment-input { + flex: 1; + padding: 10px; + background-color: var(--darker); + border: 1px solid var(--border); + color: var(--text); + font-size: 0.9rem; + resize: none; + min-height: 38px; + max-height: 100px; + overflow-y: auto; + font-family: 'Roboto Mono', monospace; +} + +.comment-input:focus { + outline: none; + border-color: var(--primary); +} + +.comment-submit { + background-color: var(--primary); + color: var(--dark); + border: none; + padding: 0 15px; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + font-family: 'Roboto Mono', monospace; +} + +.comment-submit:disabled { + background-color: #333; + color: #555; + cursor: not-allowed; +} + +.comments-loading { + text-align: center; + padding: 10px; + font-size: 0.9rem; + color: var(--text-muted); +} + +.no-comments { + text-align: center; + padding: 15px 0; + font-size: 0.9rem; + color: var(--text-muted); + font-style: italic; +} @@ -1603,6 +1735,11 @@ async function submitVote(proposalId, voteType, isPetition = false, petitionDeta `; proposalsContainer.appendChild(proposalCard); + + + + // Add comments section to the proposal card - NEW CODE + addCommentsToProposal(proposalCard, proposal.id); // Add event listeners for vote buttons const upvoteButton = proposalCard.querySelector('[data-vote-type="upvote"]'); @@ -2263,6 +2400,20 @@ function createProposalViewModal(proposal) { + `; + modalBody.innerHTML += ` +
+

Comments

+
+
+ + +
+
+
Loading comments...
+
+
+
`; modalContainer.appendChild(modalHeader); @@ -2338,6 +2489,25 @@ function createProposalViewModal(proposal) { window.open(`https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`, '_blank'); }); } + + // Handle comment form in modal + const modalCommentForm = modalBody.querySelector('.comment-form'); + const modalCommentInput = modalBody.querySelector('.comment-input'); + const modalCommentSubmit = modalBody.querySelector('.comment-submit'); + + if (modalCommentForm && modalCommentInput && modalCommentSubmit) { + modalCommentInput.addEventListener('input', () => { + checkCommentInput(modalCommentInput, modalCommentSubmit); + }); + + modalCommentForm.addEventListener('submit', (e) => { + e.preventDefault(); + submitComment(proposal.id, modalCommentForm); + }); + + // Load comments for the modal + loadComments(proposal.id); + } } function getShareableImageUrl(proposal) { @@ -2361,6 +2531,226 @@ function getShareableImageUrl(proposal) { // Combine everything return `${cloudinaryUrl}/${textParams}/${templateUrl}`; } + +// Function to toggle comments visibility +function toggleComments(proposalId, button) { + const commentsContainer = document.querySelector(`.comments-container[data-proposal-id="${proposalId}"]`); + const isExpanded = button.getAttribute('aria-expanded') === 'true'; + + if (isExpanded) { + button.setAttribute('aria-expanded', 'false'); + commentsContainer.classList.remove('show'); + } else { + button.setAttribute('aria-expanded', 'true'); + commentsContainer.classList.add('show'); + loadComments(proposalId); + } +} + +// Function to load comments for a proposal +async function loadComments(proposalId) { + const commentsContainer = document.querySelector(`.comments-container[data-proposal-id="${proposalId}"]`); + const commentsContent = commentsContainer.querySelector('.comments-content'); + + // Show loading state + commentsContent.innerHTML = `
Loading comments...
`; + + try { + const response = await fetch(`${API_BASE_URL}/comments?proposalId=${proposalId}`); + + if (!response.ok) { + throw new Error('Failed to fetch comments'); + } + + const comments = await response.json(); + renderComments(commentsContent, comments); + } catch (error) { + console.error('Error fetching comments:', error); + commentsContent.innerHTML = ` +
+ Failed to load comments. Please try again. +
+ `; + } +} + +// Function to render comments +function renderComments(container, comments) { + if (!comments || comments.length === 0) { + container.innerHTML = ` +
+ No comments yet. Be the first to comment! +
+ `; + return; + } + + let commentsHtml = ''; + + comments.forEach(comment => { + commentsHtml += ` +
+
+
${comment.user_name}
+
${formatDate(comment.timestamp)}
+
+
${formatCommentText(comment.comment_text)}
+
+ `; + }); + + container.innerHTML = commentsHtml; +} + +// Function to format comment text (converts URLs to links, etc.) +function formatCommentText(text) { + // Escape HTML to prevent XSS + const escaped = text.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + + // Convert URLs to clickable links + const withLinks = escaped.replace( + /(https?:\/\/[^\s]+)/g, + '$1' + ); + + return withLinks; +} + +// Function to submit a new comment +async function submitComment(proposalId, form) { + const commentInput = form.querySelector('.comment-input'); + const submitButton = form.querySelector('.comment-submit'); + + const commentText = commentInput.value.trim(); + + if (!commentText) return; + + // Disable form while submitting + commentInput.disabled = true; + submitButton.disabled = true; + + try { + const response = await fetch(`${API_BASE_URL}/comments`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + proposalId: proposalId, + userId: currentUser.id, + commentText: commentText + }) + }); + + if (!response.ok) { + throw new Error('Failed to submit comment'); + } + + // Clear input + commentInput.value = ''; + + // Reload comments to show the new one + const commentsContainer = document.querySelector(`.comments-container[data-proposal-id="${proposalId}"]`); + const commentsContent = commentsContainer.querySelector('.comments-content'); + + // Add new comment to the top (optimistic update) + const newComment = await response.json(); + const tempCommentHtml = ` +
+
+
${newComment.userName}
+
just now
+
+
${formatCommentText(newComment.commentText)}
+
+ `; + + // If there are no comments yet, replace the "no comments" message + if (commentsContent.querySelector('.no-comments')) { + commentsContent.innerHTML = tempCommentHtml; + } else { + // Otherwise, prepend to existing comments + commentsContent.insertAdjacentHTML('afterbegin', tempCommentHtml); + } + + } catch (error) { + console.error('Error posting comment:', error); + showToastMessage('Failed to post comment. Please try again.', '#ff0099'); + } finally { + // Re-enable form + commentInput.disabled = false; + submitButton.disabled = false; + } +} + +// Function to check if comment input is not empty +function checkCommentInput(input, submitButton) { + const hasContent = input.value.trim().length > 0; + submitButton.disabled = !hasContent; +} + +// Addition to renderProposals function - Add comments section to each proposal card +function addCommentsToProposal(proposalCard, proposalId) { + const commentsSection = document.createElement('div'); + commentsSection.className = 'comments-section'; + + const commentsToggle = document.createElement('button'); + commentsToggle.className = 'comments-toggle'; + commentsToggle.setAttribute('aria-expanded', 'false'); + commentsToggle.innerHTML = ' Show comments'; + + const commentsContainer = document.createElement('div'); + commentsContainer.className = 'comments-container'; + commentsContainer.setAttribute('data-proposal-id', proposalId); + + const commentsForm = document.createElement('form'); + commentsForm.className = 'comment-form'; + commentsForm.onsubmit = (e) => { + e.preventDefault(); + submitComment(proposalId, commentsForm); + }; + + const commentInput = document.createElement('textarea'); + commentInput.className = 'comment-input'; + commentInput.placeholder = 'Add a comment...'; + commentInput.rows = 1; + commentInput.oninput = () => checkCommentInput(commentInput, commentSubmit); + + // Auto-resize textarea as user types + commentInput.addEventListener('input', function() { + this.style.height = 'auto'; + this.style.height = (this.scrollHeight) + 'px'; + }); + + const commentSubmit = document.createElement('button'); + commentSubmit.className = 'comment-submit'; + commentSubmit.type = 'submit'; + commentSubmit.textContent = 'Post'; + commentSubmit.disabled = true; + + commentsForm.appendChild(commentInput); + commentsForm.appendChild(commentSubmit); + + const commentsContent = document.createElement('div'); + commentsContent.className = 'comments-content'; + + commentsContainer.appendChild(commentsForm); + commentsContainer.appendChild(commentsContent); + + commentsSection.appendChild(commentsToggle); + commentsSection.appendChild(commentsContainer); + + proposalCard.appendChild(commentsSection); + + // Add event listener to toggle button + commentsToggle.addEventListener('click', () => { + toggleComments(proposalId, commentsToggle); + }); +} \ No newline at end of file