added admin page
This commit is contained in:
24
dist/assets/admin-BaZpN3Uw.js
vendored
Normal file
24
dist/assets/admin-BaZpN3Uw.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import{i as C,j as A,n as p,p as D,f as b,d as S,v as k,x as M,y as $,t as x,z as T,A as F,B as Y,C as N}from"./donors-DDFnEjnq.js";const f=["pcgurudm@gmail.com"];let m=[];document.addEventListener("DOMContentLoaded",()=>{C(),H(),V(),P(),j(),_()});function H(){document.getElementById("loginGoogle").addEventListener("click",A),document.getElementById("logoutBtn").addEventListener("click",p),document.getElementById("unauthorizedLogout").addEventListener("click",p),D(q)}async function q(t){const e=document.getElementById("authSection"),n=document.getElementById("unauthorizedSection"),o=document.getElementById("adminContent"),d=document.getElementById("logoutBtn");if(!t){e.classList.remove("hidden"),n.classList.add("hidden"),o.classList.add("hidden"),d.style.display="none";return}if(d.style.display="",!await z(t.email)){e.classList.add("hidden"),n.classList.remove("hidden"),o.classList.add("hidden");return}e.classList.add("hidden"),n.classList.add("hidden"),o.classList.remove("hidden"),g()}async function z(t){try{const e=b(S,"config","admins"),n=await k(e);return n.exists()?(n.data()?.emails||[]).includes(t):f.includes(t)?(await M(e,{emails:f}),console.log("Admin allowlist created with seed admins."),!0):!1}catch(e){return console.error("Admin check failed:",e),!1}}function V(){document.querySelectorAll(".admin-tab").forEach(t=>{t.addEventListener("click",()=>{document.querySelectorAll(".admin-tab").forEach(e=>e.classList.remove("active")),document.querySelectorAll(".admin-panel").forEach(e=>e.classList.remove("active")),t.classList.add("active"),document.getElementById(`panel-${t.dataset.tab}`).classList.add("active")})})}async function g(){try{m=await $(),R(),L()}catch(t){console.error("Failed to load donors:",t),r("Failed to load donors.","error")}}function R(){const t=x(m);document.getElementById("statTotal").textContent=`$${t.toLocaleString()}`,document.getElementById("statCount").textContent=m.length}function P(){const t=document.getElementById("addDonorForm");t.addEventListener("submit",async e=>{e.preventDefault();const n=document.getElementById("addDonorBtn");n.disabled=!0,n.textContent="Adding...";try{await T({name:document.getElementById("donorName").value.trim(),amount:document.getElementById("donorAmount").value,classYear:document.getElementById("donorClassYear").value.trim(),message:document.getElementById("donorMessage").value.trim(),anonymous:document.getElementById("donorAnonymous").checked,date:document.getElementById("donorDate").value||null}),r("Donor added!","success"),t.reset(),await g()}catch(o){console.error("Failed to add donor:",o),r("Failed to add donor.","error")}finally{n.disabled=!1,n.textContent="Add Donor"}})}let u=[],h=[];function j(){document.getElementById("csvFile").addEventListener("change",G),document.getElementById("importBtn").addEventListener("click",O),document.getElementById("csvCancelBtn").addEventListener("click",I),document.querySelectorAll(".mapping-grid select").forEach(e=>{e.addEventListener("change",B)})}function G(t){const e=t.target.files[0];if(!e)return;const n=new FileReader;n.onload=o=>{const d=o.target.result,a=U(d);if(a.length<2){r("CSV file appears empty or has no data rows.","error");return}h=a[0],u=a.slice(1),W(),document.getElementById("csvMapping").classList.remove("hidden")},n.readAsText(e)}function U(t){const e=[];let n=[],o="",d=!1;for(let a=0;a<t.length;a++){const s=t[a],i=t[a+1];d?s==='"'&&i==='"'?(o+='"',a++):s==='"'?d=!1:o+=s:s==='"'?d=!0:s===","?(n.push(o.trim()),o=""):s===`
|
||||
`||s==="\r"&&i===`
|
||||
`?(n.push(o.trim()),n.some(l=>l!=="")&&e.push(n),n=[],o="",s==="\r"&&a++):o+=s}return n.push(o.trim()),n.some(a=>a!=="")&&e.push(n),e}function W(){const t=["mapName","mapAmount","mapClassYear","mapMessage","mapDate","mapAnonymous"],e={mapName:["name","donor","first","full"],mapAmount:["amount","donation","gift","total","sum"],mapClassYear:["class","year","grad"],mapMessage:["message","note","comment"],mapDate:["date","time","when"],mapAnonymous:["anonymous","anon"]};for(const n of t){const o=document.getElementById(n);o.innerHTML='<option value="">-- skip --</option>',h.forEach((s,i)=>{const l=document.createElement("option");l.value=i,l.textContent=s,o.appendChild(l)});const d=e[n],a=h.findIndex(s=>d.some(i=>s.toLowerCase().includes(i)));a!==-1&&(o.value=a)}B()}function E(t){const e=n=>{const o=document.getElementById(n).value;return o!==""&&t[Number(o)]||""};return{name:e("mapName"),amount:e("mapAmount"),classYear:e("mapClassYear"),message:e("mapMessage"),date:e("mapDate"),anonymous:e("mapAnonymous")}}function B(){const t=document.getElementById("csvPreviewHead"),e=document.getElementById("csvPreviewBody");t.innerHTML="<tr><th>Name</th><th>Amount</th><th>Class Year</th><th>Date</th><th>Message</th><th>Anon</th></tr>";const n=u.slice(0,5);e.innerHTML=n.map(o=>{const d=E(o);return`<tr>
|
||||
<td>${c(d.name)}</td>
|
||||
<td>${c(d.amount)}</td>
|
||||
<td>${c(d.classYear)}</td>
|
||||
<td>${c(d.date)}</td>
|
||||
<td>${c(d.message)}</td>
|
||||
<td>${y(d.anonymous)?"Yes":"No"}</td>
|
||||
</tr>`}).join(""),document.getElementById("previewCount").textContent=`(${u.length} rows total)`}function y(t){if(!t)return!1;const e=t.toString().toLowerCase().trim();return e==="true"||e==="yes"||e==="1"}async function O(){const t=document.getElementById("importBtn"),e=document.getElementById("importProgress"),n=document.getElementById("progressFill"),o=document.getElementById("progressText"),d=u.map(a=>{const s=E(a),i=parseFloat(s.amount.replace(/[^0-9.\-]/g,""));return!s.name&&!y(s.anonymous)||isNaN(i)||i<=0?null:{name:s.name||"Anonymous",amount:i,classYear:s.classYear,message:s.message,date:s.date||null,anonymous:y(s.anonymous)}}).filter(Boolean);if(d.length===0){r("No valid rows to import. Check your column mapping.","error");return}if(confirm(`Import ${d.length} donors? (${u.length-d.length} rows will be skipped due to missing name/amount)`)){t.disabled=!0,e.classList.remove("hidden");try{let s=0;for(let i=0;i<d.length;i+=500){const l=d.slice(i,i+500);await F(l),s+=l.length;const w=Math.round(s/d.length*100);n.style.width=w+"%",o.textContent=`Imported ${s} of ${d.length}...`}r(`Successfully imported ${s} donors!`,"success"),I(),await g()}catch(a){console.error("Import failed:",a),r("Import failed. Some rows may have been imported.","error")}finally{t.disabled=!1,e.classList.add("hidden"),n.style.width="0%"}}}function I(){u=[],h=[],document.getElementById("csvFile").value="",document.getElementById("csvMapping").classList.add("hidden")}function L(t=""){const e=document.getElementById("donorTableBody"),n=document.getElementById("donorListEmpty"),o=t?m.filter(a=>a.name.toLowerCase().includes(t.toLowerCase())):m;if(o.length===0){e.innerHTML="",n.classList.remove("hidden");return}n.classList.add("hidden"),e.innerHTML=o.map(a=>{const s=a.date?.toDate?a.date.toDate().toLocaleDateString():"";return`<tr>
|
||||
<td>${c(a.name)}${a.anonymous?' <span class="anon-badge">Anon</span>':""}</td>
|
||||
<td>$${(a.amount||0).toLocaleString()}</td>
|
||||
<td>${c(a.classYear)}</td>
|
||||
<td>${s}</td>
|
||||
<td class="msg-cell">${c(a.message)}</td>
|
||||
<td class="action-cell">
|
||||
<button class="btn-icon btn-edit" data-id="${a.id}" title="Edit">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
</button>
|
||||
<button class="btn-icon btn-delete" data-id="${a.id}" title="Delete">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>`}).join(""),e.querySelectorAll(".btn-edit").forEach(a=>{a.addEventListener("click",()=>J(a.dataset.id))}),e.querySelectorAll(".btn-delete").forEach(a=>{a.addEventListener("click",()=>Q(a.dataset.id))});const d=document.getElementById("donorSearch");d.removeEventListener("input",v),d.addEventListener("input",v)}function v(t){L(t.target.value.trim())}async function Q(t){if(confirm("Delete this donor? This cannot be undone."))try{await Y(t),r("Donor deleted.","success"),await g()}catch(e){console.error("Delete failed:",e),r("Failed to delete donor.","error")}}function _(){const t=document.getElementById("editModal");document.getElementById("editModalClose").addEventListener("click",()=>t.classList.add("hidden")),t.addEventListener("click",e=>{e.target===t&&t.classList.add("hidden")}),document.getElementById("editDonorForm").addEventListener("submit",async e=>{e.preventDefault();const n=document.getElementById("editDonorId").value,o=e.target.querySelector('button[type="submit"]');o.disabled=!0,o.textContent="Saving...";try{await N(n,{name:document.getElementById("editAnonymous").checked?"Anonymous":document.getElementById("editName").value.trim(),amount:Number(document.getElementById("editAmount").value),classYear:document.getElementById("editClassYear").value.trim(),message:document.getElementById("editMessage").value.trim(),anonymous:document.getElementById("editAnonymous").checked}),r("Donor updated!","success"),t.classList.add("hidden"),await g()}catch(d){console.error("Update failed:",d),r("Failed to update donor.","error")}finally{o.disabled=!1,o.textContent="Save Changes"}})}function J(t){const e=m.find(n=>n.id===t);e&&(document.getElementById("editDonorId").value=t,document.getElementById("editName").value=e.name||"",document.getElementById("editAmount").value=e.amount||"",document.getElementById("editClassYear").value=e.classYear||"",document.getElementById("editMessage").value=e.message||"",document.getElementById("editAnonymous").checked=!!e.anonymous,document.getElementById("editModal").classList.remove("hidden"))}function c(t){const e=document.createElement("div");return e.textContent=t||"",e.innerHTML}function r(t,e="info"){const n=document.querySelector(".toast");n&&n.remove();const o=document.createElement("div");o.className=`toast ${e}`,o.textContent=t,document.body.appendChild(o),setTimeout(()=>o.remove(),4e3)}
|
||||
1
dist/assets/admin-r6G2dek0.css
vendored
Normal file
1
dist/assets/admin-r6G2dek0.css
vendored
Normal file
File diff suppressed because one or more lines are too long
36
dist/assets/donors-DDFnEjnq.js
vendored
Normal file
36
dist/assets/donors-DDFnEjnq.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-Cpged3wR.css
vendored
1
dist/assets/index-Cpged3wR.css
vendored
File diff suppressed because one or more lines are too long
67
dist/assets/index-DWzoJss_.js
vendored
67
dist/assets/index-DWzoJss_.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/main-BYboBf7A.css
vendored
Normal file
1
dist/assets/main-BYboBf7A.css
vendored
Normal file
File diff suppressed because one or more lines are too long
32
dist/assets/main-f9-9VaoM.js
vendored
Normal file
32
dist/assets/main-f9-9VaoM.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import{c as p,d as u,q as v,o as E,a as k,b,s as L,e as T,f as A,l as U,w as g,g as D,i as M,h as N,j as H,k as F,m as R,n as q,p as P,r as j,t as W,u as B}from"./donors-DDFnEjnq.js";const I=p(u,"comments");let y=null;function Y(t){const n=v(I,E("timestamp","desc"));return y=k(n,o=>{const e=[];o.forEach(s=>{e.push({id:s.id,...s.data()})}),t(e)},o=>{console.error("Error fetching comments:",o),t([])}),y}async function _(t,n){return!t||!n.trim()?null:b(I,{userId:t.uid,userName:t.displayName||"Anonymous",userPhoto:t.photoURL||"",text:n.trim(),timestamp:L()})}async function G(t){return T(A(u,"comments",t))}const O=p(u,"students");async function J(t){const n=t.trim().toLowerCase();if(!n)return[];const o=n+"",e=v(O,g("nameLower",">=",n),g("nameLower","<=",o),E("nameLower"),U(50));try{const s=await D(e),a=[];return s.forEach(r=>{a.push({id:r.id,...r.data()})}),a}catch(s){return console.error("Error searching students:",s),[]}}const z=p(u,"subscribers");async function X(t){return b(z,{email:t,subscribedAt:L()})}let C=[],h=[];document.addEventListener("DOMContentLoaded",()=>{M(),K(),Q(),V(),Z(),nt(),st(),ct(),dt(),it(),N(ot),Y(at)});function K(){const t=document.getElementById("tabNav");t.addEventListener("click",n=>{const o=n.target.closest(".tab-btn");if(!o)return;t.querySelectorAll(".tab-btn").forEach(s=>s.classList.remove("active")),o.classList.add("active"),document.querySelectorAll(".tab-panel").forEach(s=>s.classList.remove("active"));const e=document.getElementById(`tab-${o.dataset.tab}`);e&&e.classList.add("active")})}function Q(){const t=document.getElementById("carouselTrack"),n=t.querySelectorAll(".carousel-slide"),o=document.querySelectorAll(".dot"),e=document.getElementById("carouselPrev"),s=document.getElementById("carouselNext");let a=0,r;function c(l){a=(l%n.length+n.length)%n.length,t.style.transform=`translateX(-${a*100}%)`,o.forEach((x,$)=>x.classList.toggle("active",$===a))}function d(){S(),r=setInterval(()=>c(a+1),5e3)}function S(){clearInterval(r)}e.addEventListener("click",()=>{c(a-1),d()}),s.addEventListener("click",()=>{c(a+1),d()}),o.forEach(l=>{l.addEventListener("click",()=>{c(Number(l.dataset.index)),d()})}),d()}function V(){const t=window.location.href,n="Wesley High School - 100th Anniversary Fundraiser",o="Support Wesley High School's 100th Anniversary Celebrations! Help us celebrate a century of excellence.";document.getElementById("shareFacebook").addEventListener("click",()=>{window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(t)}`,"_blank","width=600,height=400")}),document.getElementById("shareTwitter").addEventListener("click",()=>{window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent(o)}&url=${encodeURIComponent(t)}`,"_blank","width=600,height=400")}),document.getElementById("shareEmail").addEventListener("click",()=>{window.location.href=`mailto:?subject=${encodeURIComponent(n)}&body=${encodeURIComponent(o+`
|
||||
|
||||
`+t)}`})}function Z(){document.getElementById("loginGoogle").addEventListener("click",H),document.getElementById("loginFacebook").addEventListener("click",F),document.getElementById("loginTwitter").addEventListener("click",R),document.getElementById("logoutBtn").addEventListener("click",q),P(tt)}function tt(t){const n=document.getElementById("authPrompt"),o=document.getElementById("commentForm"),e=document.getElementById("userAvatar"),s=document.getElementById("userName");t?(n.classList.add("hidden"),o.classList.remove("hidden"),e.src=t.photoURL||et(t.displayName||"U"),e.alt=t.displayName||"User",s.textContent=t.displayName||t.email||"User"):(n.classList.remove("hidden"),o.classList.add("hidden"))}function et(t){const n=t.charAt(0).toUpperCase(),o=document.createElement("canvas");o.width=80,o.height=80;const e=o.getContext("2d");return e.fillStyle="#1a1a6e",e.fillRect(0,0,80,80),e.fillStyle="#ffffff",e.font="bold 36px Inter, sans-serif",e.textAlign="center",e.textBaseline="middle",e.fillText(n,40,40),o.toDataURL()}function nt(){const t=document.getElementById("commentText"),n=document.getElementById("charCount"),o=document.getElementById("postCommentBtn");t.addEventListener("input",()=>{n.textContent=`${t.value.length}/500`}),o.addEventListener("click",async()=>{const e=B(),s=t.value.trim();if(!(!e||!s)){o.disabled=!0,o.textContent="Posting...";try{await _(e,s),t.value="",n.textContent="0/500"}catch(a){console.error("Error posting comment:",a),m("Failed to post comment. Please try again.","error")}finally{o.disabled=!1,o.textContent="Post Comment"}}})}function ot(t){C=t;const n=W(t);document.getElementById("totalRaised").textContent=`$${n.toLocaleString()}`,w()}function st(){document.getElementById("donorSort").addEventListener("change",w)}function w(){const t=document.getElementById("donorsList"),n=document.getElementById("donorsEmpty"),o=document.getElementById("donorSort").value,e=j(C,o);if(e.length===0){t.innerHTML="",t.appendChild(n),n.classList.remove("hidden");return}t.innerHTML=e.map(s=>{const a=(s.name||"A").charAt(0).toUpperCase(),r=s.date?.toDate?s.date.toDate().toLocaleDateString():"",c=s.classYear?` · Class of ${s.classYear}`:"";return`
|
||||
<div class="donor-card">
|
||||
<div class="donor-info">
|
||||
<div class="donor-avatar">${a}</div>
|
||||
<div class="donor-details">
|
||||
<h4>${i(s.name)}</h4>
|
||||
<p>${r}${c}</p>
|
||||
${s.message?`<p style="margin-top:4px;color:#374151;font-size:0.85rem">"${i(s.message)}"</p>`:""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="donor-amount">$${(s.amount||0).toLocaleString()}</div>
|
||||
</div>`}).join("")}function at(t){h=t,rt()}function rt(){const t=document.getElementById("commentsList"),n=document.getElementById("commentsEmpty"),o=B();if(h.length===0){t.innerHTML="",t.appendChild(n),n.classList.remove("hidden");return}t.innerHTML=h.map(e=>{const s=e.timestamp?.toDate?lt(e.timestamp.toDate()):"just now",a=e.userPhoto?`<img class="comment-avatar" src="${i(e.userPhoto)}" alt="${i(e.userName)}" />`:`<div class="comment-avatar-placeholder">${(e.userName||"U").charAt(0).toUpperCase()}</div>`,r=o&&o.uid===e.userId?`<button class="comment-delete" data-id="${e.id}">Delete</button>`:"";return`
|
||||
<div class="comment-card">
|
||||
${a}
|
||||
<div class="comment-body">
|
||||
<div class="comment-header">
|
||||
<span class="comment-author">${i(e.userName)}</span>
|
||||
<span class="comment-time">${s}</span>
|
||||
</div>
|
||||
<p class="comment-text">${i(e.text)}</p>
|
||||
${r}
|
||||
</div>
|
||||
</div>`}).join(""),t.querySelectorAll(".comment-delete").forEach(e=>{e.addEventListener("click",async()=>{if(confirm("Delete this comment?"))try{await G(e.dataset.id)}catch(s){console.error("Error deleting comment:",s),m("Failed to delete comment.","error")}})})}function it(){const t=document.getElementById("studentSearch"),n=document.getElementById("studentResults"),o=document.getElementById("studentSearchEmpty");let e;t.addEventListener("input",()=>{clearTimeout(e);const s=t.value.trim();if(!s){n.innerHTML="",n.appendChild(o),o.querySelector("p").textContent="Enter a name above to search for students.",o.classList.remove("hidden");return}e=setTimeout(async()=>{n.innerHTML='<div class="search-loading"><div class="loading-spinner"></div> Searching...</div>';const a=await J(s);if(a.length===0){n.innerHTML="",n.appendChild(o),o.querySelector("p").textContent=`No students found matching "${i(s)}".`,o.classList.remove("hidden");return}n.innerHTML=a.map(r=>`
|
||||
<div class="student-card">
|
||||
<div class="student-avatar">${(r.name||"?").charAt(0).toUpperCase()}</div>
|
||||
<div class="student-info">
|
||||
<h4>${i(r.name)}</h4>
|
||||
<p>Class of ${i(String(r.classYear||"N/A"))}</p>
|
||||
</div>
|
||||
</div>`).join("")},350)})}function ct(){const t=document.getElementById("newsletterForm"),n=t.querySelector('button[type="submit"]');t.addEventListener("submit",async o=>{o.preventDefault();const e=document.getElementById("newsletterEmail").value.trim();if(e){n.disabled=!0,n.textContent="Joining...";try{await X(e),m("Thank you for subscribing!","success"),t.reset()}catch(s){console.error("Newsletter subscribe error:",s),m("Failed to subscribe. Please try again.","error")}finally{n.disabled=!1,n.textContent="Join"}}})}function dt(){const t=document.getElementById("modalOverlay");document.getElementById("modalClose").addEventListener("click",()=>t.classList.add("hidden")),t.addEventListener("click",e=>{e.target===t&&t.classList.add("hidden")}),document.getElementById("becomeFundraiserBtn").addEventListener("click",()=>{f('<h2>Become a Fundraiser</h2><p style="margin-top:12px">Create your own fundraising page and rally your classmates! Share the donation link with friends and family to help Wesley High School reach its goal.</p><a href="https://pay.shopdm.store/dominica-methodist-circuit" target="_blank" rel="noopener" class="btn btn-primary" style="margin-top:20px;display:inline-flex">Start Fundraising</a>')});const o=document.getElementById("startFundraiserBtn");o&&o.addEventListener("click",()=>{f('<h2>Start Your Fundraiser</h2><p style="margin-top:12px">Share the Wesley High School donation page with your network and help us celebrate 100 years!</p><a href="https://pay.shopdm.store/dominica-methodist-circuit" target="_blank" rel="noopener" class="btn btn-primary" style="margin-top:20px;display:inline-flex">Get Started</a>')})}function f(t){document.getElementById("modalContent").innerHTML=t,document.getElementById("modalOverlay").classList.remove("hidden")}function i(t){const n=document.createElement("div");return n.textContent=t||"",n.innerHTML}function lt(t){const n=Math.floor((Date.now()-t.getTime())/1e3);if(n<60)return"just now";const o=Math.floor(n/60);if(o<60)return`${o}m ago`;const e=Math.floor(o/60);if(e<24)return`${e}h ago`;const s=Math.floor(e/24);if(s<30)return`${s}d ago`;const a=Math.floor(s/30);return a<12?`${a}mo ago`:`${Math.floor(a/12)}y ago`}function m(t,n="info"){const o=document.querySelector(".toast");o&&o.remove();const e=document.createElement("div");e.className=`toast ${n}`,e.textContent=t,document.body.appendChild(e),setTimeout(()=>e.remove(),4e3)}
|
||||
Reference in New Issue
Block a user