From 64fd0131543d225954c070a08a871853591f44b6 Mon Sep 17 00:00:00 2001 From: Folarex10 Date: Sat, 14 Mar 2026 12:59:20 +0000 Subject: [PATCH 01/21] Landing page css renamed --- HowItWorks.html | 2 +- style.css => LandingPageStyle.css | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename style.css => LandingPageStyle.css (100%) diff --git a/HowItWorks.html b/HowItWorks.html index 4bd5661..10c6678 100644 --- a/HowItWorks.html +++ b/HowItWorks.html @@ -10,7 +10,7 @@ - +
diff --git a/style.css b/LandingPageStyle.css similarity index 100% rename from style.css rename to LandingPageStyle.css From d1dbaeaf98cb3923c8ecedc102f91a795ed275f6 Mon Sep 17 00:00:00 2001 From: Timothy Olaleye Date: Sat, 14 Mar 2026 13:07:28 +0000 Subject: [PATCH 02/21] Update style.css --- style.css | 443 ------------------------------------------------------ 1 file changed, 443 deletions(-) diff --git a/style.css b/style.css index d41279a..2c0ed31 100644 --- a/style.css +++ b/style.css @@ -1,446 +1,3 @@ - -*{ - margin:0; - padding:0; - box-sizing:border-box; -} - -header{ - margin-bottom: 60px; -} - -body{ - background:#f4f6f9; - padding:40px 60px; - color:#243a5e; - font-family: "Poppins", sans-serif; -} - - - -.navbar{ - display:flex; - justify-content:space-between; - align-items:center; -} - -.logo{ - display:flex; - align-items:center; - gap:10px; - font-size:50px; - font-weight:700; -} - - -.logo img{ - width:150px; -} - - -.nav-links{ - display:flex; - gap:30px; - list-style:none; -} - -.nav-links a{ - text-decoration:none; - color:#243a5e; - font-weight:600; -} - -.about:hover, .contact:hover, .current:hover, .login:hover { - background-color: powderblue; - transform: translateY(-3px); -} - -/* .nav-links:hover { - background-color: #e008aa; - transform: translateY(-3px); -} */ - -.nav-buttons{ - display:flex; - gap:15px; -} - -/* .login{ - background:none; - border:none; - font-weight:600; - cursor:pointer; -} */ -/* -.register{ - background:#243a5e; - color:white; - border:none; - padding:10px 20px; - border-radius:10px; - cursor:pointer; -} */ - - - -.hero{ - display:flex; - align-items:center; - justify-content:space-between; - gap:60px; -} - -.hero-text{ - margin-top: -11px; - max-width:650px; -} - -.title{ - - font-size:50px; - font-weight:700; -} - -.title-large{ - - font-size:50px; - font-weight:700; - margin:-12px 0; -} - -.orange{ - color:#FFB347; -} - -.green{ - color:#1FDD19; -} - -.description{ - font-size:18px; - color:#6b7c93; - line-height:1.6; -} - -.hero-buttons{ - margin-top:20px; - display:flex; - gap:50px; -} - -.primary-btn{ - background:#243a5e; - color:white; - border:none; - padding:10px 20px; - border-radius:15px; - cursor:pointer; -} - -.primary-btn:hover { - background-color: #e6c200; - transform: translateY(-3px); -} - -/* .secondary-btn{ - background:#243a5e; - color:#f4a742; - border:none; - padding:16px 28px; - border-radius:16px; - cursor:pointer; -} */ - -.hero-image img{ - width:550px; - border-top-left-radius: 50px; - border-bottom-right-radius: 50px; - /* border-radius:20px; */ - box-shadow:0 10px 30px rgba(0,0,0,0.1); -} - - -.how-it-works { - padding: 5rem 2rem; - /* background: #f9f9f9; - color: #333; */ -} - -.how-it-works-title { - text-align: center; -} - -.how-it-works-title h2 { - font-size: 3.2rem; - font-weight: 500; -} - -/* .how-it-works-card-container { - display: flex; - gap: 2rem; - margin-top: 1rem; - -} */ - -.how-it-works-card-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 30px; - /* max-width: 1000px; */ - margin: 0 auto; -} - - -/* @media (max-width: 640px) { - .how-it-works-card-container { - flex-direction: column; - } -} */ - - - -.how-it-works-card { - background: #fff; - border-radius: 10px; - padding: 20px; - width:280px; - box-shadow: 0 4px 10px rgba(0,0,0,0.1); - transition: 0.3s ease; - align-items: center; - display: flex; - flex-direction: column; - gap: 1rem; - border: #1f3a5f solid 3px; - -} - -.how-it-works-card:hover { - transform: translateY(-5px); -} - -.how-it-works-card h4 { - font-weight: 600; - font-size: 1.2rem; -} - -.how-it-works-card p { - color: #555; - line-height: 1.5; -} - - - -.card-icon1 { - display: flex; - justify-content: center; - align-items: center; -} - - - - -/* .Benefits { - /* padding: 50px 20px; - -/* } */ - - -.Benefits-title h2 { - font-size: 4.2rem; - /* font-weight: 500; */ - -} - -.Benefits-card-container { - display: grid; - gap:30px; - - -} - -/* .Benefits-card-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 30px; - max-width: 1000px; - margin: 0 auto; -} */ - -/* @media (max-width: 1000px) { - .Benefits-card-container { - flex-direction: column; - } -} */ - -.Benefits-card { - justify-content: space-between; - /* border: 3px; - border-color: #1FDD19; */ - /* padding: 20px; */ -} - -.Benefits-card h5 { - margin-top: -110px; - text-align: left; - /* max-width: 75%; */ - /* font-weight: 400; */ - font-size: 2.5rem; - color: #1f3a5f; -} - -.Benefits-card p { - text-align: left; - /* max-width: 75%; */ - /* padding: 10px; */ - /* margin-left: -20px; */ - color: #1f3a5f; - -} - -.card-icon { - display: flex; - - background-color: #fff; - border: #ffb347 solid 2px; - width: 11rem; - height: 11rem; - justify-content: center; - align-items: center; - border-radius: 90px; - color: #057DF2; - margin-left:55rem; - /* float: right; */ - - -} - -.line{ - border:3px; - height: 2px; - background-color: #057df2; - margin: 30px 0; - width: 750px; -} - -.start-event{ - margin-top: 50px; - display: flex; - justify-content: center; - align-items: center; - text-align: center; - min-height: 70vh; - padding: 20px; -} - -.start-event-content{ - max-width: 1000px; - color: #2c4a6f; - -} - -.start-event-title { - color: #2c4a6f; -} - -.start-event-content h3 { - font-size: 2.5rem; - margin-bottom: 20px; -} - -.start-event-content p { - font-size: 1.2rem; - margin-bottom: 30px; - line-height: 1.6; -} - - -.footer{ - margin-top: 10px; - background:#2c4a6f; - color:white; - padding:20px 10%; -} - -.footer-container{ - display:flex; - justify-content:space-between; - flex-wrap:wrap; - gap:30px; -} - -.footer-col{ - flex:1; - min-width:200px; -} - -.logo{ - font-size:20px; - font-weight:bold; - margin-bottom:10px; -} - -.tagline{ - color:#cbd5e1; - margin-bottom: 100px; - font-size: 15px; -} - -.footer-col h3{ - margin-bottom:15px; - font-size:18px; -} - -.footer-col ul{ - list-style:none; -} - -.footer-col ul li{ - margin-bottom:10px; -} - -.footer-col ul li a{ - color:#cbd5e1; - text-decoration:none; - transition:0.3s; -} - -.footer-col ul li a:hover{ - color:white; -} - -.social-icons{ - display:flex; - gap:15px; - margin-top:10px; - margin-bottom:15px; -} - -.social-icons a{ - background:white; - color:#2c4a6f; - width:40px; - height:40px; - display:flex; - align-items:center; - justify-content:center; - border-radius:6px; - font-size:18px; - text-decoration:none; - transition:0.3s; -} - -.social-icons a:hover{ - background:#dbeafe; -} - -.help-text{ - color:#cbd5e1; -} - -.copyright{ - font-size:10px; - color:#cbd5e1; -} * { box-sizing: border-box; margin: 0; From 5b4efd6f12a0951effa81132841ff6bb0d25415f Mon Sep 17 00:00:00 2001 From: hamlad18 Date: Thu, 19 Mar 2026 12:09:14 -0200 Subject: [PATCH 03/21] i "new changes" --- IMG-20260310-WA0010.jpg | Bin 0 -> 94105 bytes index.html | 32 +++--- index2.html | 86 +++++++++++++++ script.js | 53 ++++++++++ script2.js | 44 ++++++++ style.css | 45 +++++++- style2.css | 228 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 474 insertions(+), 14 deletions(-) create mode 100644 IMG-20260310-WA0010.jpg create mode 100644 index2.html create mode 100644 script2.js create mode 100644 style2.css diff --git a/IMG-20260310-WA0010.jpg b/IMG-20260310-WA0010.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7fbe358c78ab2838f1e6039d7e675499122669e2 GIT binary patch literal 94105 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�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 Aid Loop Organizational login + + + + + +

- - + \ No newline at end of file diff --git a/index2.html b/index2.html new file mode 100644 index 0000000..5a31006 --- /dev/null +++ b/index2.html @@ -0,0 +1,86 @@ + + + + + + Organizers Account + + + + + + + +
aidloop
+
+
+

Sign Up

+

Create an Organizers account to start hosting volunteer events on Aidloop

+ +
+
Organization Information
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
Location
+
+
+ + +
+
+ + +
+
+
+ +
+
Organization details
+
+
+ + +
+
+ + +

Provide a link to your organizations Social media page

+
+
+
+ + +
+ +

Already have an account? Login

+
+ +
+ + + \ No newline at end of file diff --git a/script.js b/script.js index e69de29..966f5dd 100644 --- a/script.js +++ b/script.js @@ -0,0 +1,53 @@ +document.getElementById('loginForm').addEventListener('submit', function(event) { + const email = document.getElementById('email').value; + const phone = document.getElementById('phone').value; + const errorMsg = document.getElementById('errorMsg'); + const messageDiv = document.getElementById('message'); + + // Clear previous messages + messageDiv.className = 'error-message'; + + // Validation + if (Email === '' || password === '' ) { + messageDiv.textContent = 'Please fill in all fields'; + return; + } + + if (Email.length < 14) { + messageDiv.textContent = 'Email must be at least 14 characters'; + return; + } + + // Demo credentials (in real app, this would be server-side) + const validEmail = 'demo@example.com'; + // Regex for phone (simple 10 digits) + const phoneRegex = /^[0-9]{10}$/; + + if (!phoneRegex.test(phone)) { + errorMsg.style.display = 'block'; + errorMsg.innerText = 'Phone number must be 10 digits.'; + event.preventDefault(); // stop form submission + } else { + errorMsg.style.display = 'none'; + alert("phone number Validated!") + } + + // Check credentials + if (Email === validEmail && phone === validphone) { + messageDiv.className = 'success-message'; + messageDiv.textContent = 'Login Successful! Redirecting...'; + + setTimeout(() => { + alert('Welcome, ' + Email + '!'); + }, 2000); + + } else { + messageDiv.textContent = 'Invalid Email or Phone Number'; + } +}) +// Allow Enter key to submit +document.addEventListener('keypress', function(event){ + if (event.key === 'Enter') { + login(); + } +}); \ No newline at end of file diff --git a/script2.js b/script2.js new file mode 100644 index 0000000..b3b6e57 --- /dev/null +++ b/script2.js @@ -0,0 +1,44 @@ +document.getElementById('form').addEventListener('submit', function(event) +{ + event.preventDefault(); + const name = document.getElementById('name').value; + const email = document.getElementById('email').value; + const phoneNumberInput = document.getElementById('phone'); + const messageDiv = document.getElementById('MessageDiv'); + const phoneNumber = phoneNumberInput.value.trim(); + const errorElement = document.getElementById('phoneError'); + + + // Regular expression for common Nigerian phone number formats + // Matches: 070/080/081/090/091 followed by 8 digits OR +234 followed by 10 digits + const nigerianPhoneRegex = /^(0[789][01]\d{8}|\+234[789][01]\d{8})$/ + + messageDiv.className = 'error-message'; + + if (name === "" || email === "" || phoneNumber === "") { + messageDiv.textContent = 'Please fill in all fields'; + return; + } + + if (email.length < 14) { + messageDiv.textContent = 'Email must be at least 14 characters'; + return; + } + + if (nigerianPhoneRegex.test(phoneNumber)) { + // Number is valid + errorElement.style.display = 'none'; + alert('Valid Nigerian Phone Number: ' + phoneNumber); + //Here you can proceed with form submission to a server + // e.g., this.submit(); + } else { + // Number is invalid + errorElement.style.display = 'inline'; + } +}); + +document.addEventListener('keypress', function(event) { + if (event.key === 'Enter') { + signup(); + } +}); \ No newline at end of file diff --git a/style.css b/style.css index 2c0ed31..08c7961 100644 --- a/style.css +++ b/style.css @@ -6,7 +6,7 @@ body { background-color: #f6f3f2; - font-family: sans-serif; + font-family: "Poppins", sans-serif; display: flex; justify-content: center; height: 100vh; @@ -72,10 +72,42 @@ body { height: 40px; border: 2px solid #999; border-radius: 10px; + cursor: pointer; } +.phone-container { + display: flex; + flex-direction: column; + + +} +.phone-container label { + align-self: flex-start; + font-size: 10px; + font-weight: 400; + color: black; + margin-bottom: 10px; + position: relative; + +} + +.phone-container input { + display: inline-block; + width: 300px; + height: 40px; + border: 2px solid #999; + border-radius: 10px; + cursor: pointer; + +} + + .phone-container i { + position: absolute; + align-self: flex-end; +} + .btn-container { display: flex; flex-direction: column; @@ -91,9 +123,18 @@ body { background-color: #1F3A5F; border-radius: 15px; color: white; - font-size: 15px; + font-size: 15px; + border: none; } +.btn-org:hover { + background-color: #fff; + color: black; + border: 2px solid #1F3A5F ; +} + + + .link { display: flex; gap: 20px; diff --git a/style2.css b/style2.css new file mode 100644 index 0000000..98267d2 --- /dev/null +++ b/style2.css @@ -0,0 +1,228 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Poppins", sans-serif; +} + +.logo-wrapper { + display: flex; + justify-content: center; + align-items: center; +} + +img { + width: 200px; + height: 150px; +} + +.container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 20px; + +} + +.header-container { + text-align: center; + border-bottom: 1px solid #1F3A5F; +} + +h1 { + text-align: center; +} + +#cr-org { + font-size: 10px; + color: #6B7C93; + padding: 10px; + +} + +.title-header { + background-color: #6B7C93; + padding: 10px; + width: 300px; + height: 40px; + text-align: center; + color: white; + justify-self: center; + align-self: center; + border-radius: 3px; +} + +.form { + display: flex; + flex-direction: row; + width: 100%; +} + +@media (max-width:608px) { + .form { + flex-direction: column; + gap: 20px; + } +} + +.form-group { + display: flex; + gap:2px ; + flex-direction: column; + padding: 0 40px; +} + +.form-group label { + font-size: 10px; + font-weight: 500; +} + +.form-group input { + width: 250px; + height: 25px; + padding-left: 10px; +} + + +.geo-info { + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; + +} + +.select-group { + display: flex; + justify-content: center; + align-items: center; + gap: 400px; + +} + +@media (max-width:608px) { + .select-group { + flex-direction: column; + gap: 20px; + } +} + +.select { + display: flex; + flex-direction: column; +} + +.select label { + font-size: 10px; + font-weight: 500; +} + +.select input { + width: 300px; + height: 25px; +} + +.org-details { + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; +} + +.details-group { + display: flex; + justify-content: center; + gap: 40px; +} + +@media (max-width:608px) { + .details-group { + flex-direction: column; + gap: 20px; + } +} +.org-d { + display: flex; + flex-direction: column; +} + +.org-d label { + font-size: 10px; + font-weight: 500; +} + +@media (max-width:608px) { + .org-d label { + align-self: center ; + } +} + +.org-d textarea { + width: 600px; + height: 100px; +} + +@media (max-width:608px) { + .org-d textarea { + width: 300px ; + height: 100px; + align-self: center; + } +} + +.org-d input { + width: 400px; + +} + +@media (max-width:608px) { + .org-d input { + width: 300px ; + align-self: center; + } +} + +.org-d p { + font-size: 10px; + margin-top: 10px; +} + +@media (max-width:608px) { + .org-d p { + align-self: center; + } +} + +.btn-container { + display: block; + align-items: center; +} + + +.btn-sign { + width: 700px; + height: 40px; + background-color: #1F3A5F; + color: white; + border: none; + border-radius: 3px; +} + +@media (max-width:608px) { + .btn-sign { + width: 300px ; + } +} + +.btn-container p { + text-align: center; + margin-top: 10px; +} + +.btn-container a { + text-decoration: none; +} \ No newline at end of file From d3d64d806b1e98944ac00bb5efb645d016312104 Mon Sep 17 00:00:00 2001 From: Oluwasayo Alabi Date: Fri, 20 Mar 2026 18:01:32 +0100 Subject: [PATCH 04/21] My local work before pulling updates --- css/components.css | 0 css/global.css | 0 css/pages | 0 css/style.css | 0 index.html | 0 js/api | 0 js/auth.js | 0 js/pages | 0 login.html | 0 organizer | 0 register-organizer.html | 0 11 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 css/components.css create mode 100644 css/global.css create mode 100644 css/pages create mode 100644 css/style.css create mode 100644 index.html create mode 100644 js/api create mode 100644 js/auth.js create mode 100644 js/pages create mode 100644 login.html create mode 100644 organizer create mode 100644 register-organizer.html diff --git a/css/components.css b/css/components.css new file mode 100644 index 0000000..e69de29 diff --git a/css/global.css b/css/global.css new file mode 100644 index 0000000..e69de29 diff --git a/css/pages b/css/pages new file mode 100644 index 0000000..e69de29 diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..e69de29 diff --git a/index.html b/index.html new file mode 100644 index 0000000..e69de29 diff --git a/js/api b/js/api new file mode 100644 index 0000000..e69de29 diff --git a/js/auth.js b/js/auth.js new file mode 100644 index 0000000..e69de29 diff --git a/js/pages b/js/pages new file mode 100644 index 0000000..e69de29 diff --git a/login.html b/login.html new file mode 100644 index 0000000..e69de29 diff --git a/organizer b/organizer new file mode 100644 index 0000000..e69de29 diff --git a/register-organizer.html b/register-organizer.html new file mode 100644 index 0000000..e69de29 From 1daf19e6a7eefd5f2081c7d74857df165c225453 Mon Sep 17 00:00:00 2001 From: hamlad18 Date: Mon, 23 Mar 2026 01:29:49 -0200 Subject: [PATCH 05/21] new files added --- Frame 534.png | Bin 0 -> 1236 bytes Frame 750.png | Bin 0 -> 1651 bytes Frame 751.png | Bin 0 -> 1660 bytes Vector (1).png | Bin 0 -> 801 bytes Vector (2).png | Bin 0 -> 456 bytes bi_building-check.png | Bin 0 -> 1026 bytes clarity_certificate-solid.png | Bin 0 -> 816 bytes fluent_people-20-regular.png | Bin 0 -> 1430 bytes healthicons_ui-user-profile-outline.png | Bin 0 -> 2023 bytes hugeicons_checkmark-circle-02.png | Bin 0 -> 1544 bytes ic_baseline-search.png | Bin 0 -> 1011 bytes ic_outline-schedule.png | Bin 0 -> 1345 bytes icons8_plus (1).png | Bin 0 -> 812 bytes icons8_plus.png | Bin 0 -> 812 bytes index2.html | 2 +- index3.html | 96 +++ index4.html | 96 +++ logo aidloop trans.png | Bin 0 -> 9059 bytes logo aidloop transs.png | Bin 0 -> 8299 bytes material-symbols-light_logout-rounded.png | Bin 0 -> 471 bytes qlementine-icons_home-24.png | Bin 0 -> 659 bytes script.js | 29 +- script2.js | 12 +- script3.js | 718 ++++++++++++++++++++++ style.css | 2 + style3.css | 209 +++++++ uit_calendar.png | Bin 0 -> 550 bytes 27 files changed, 1149 insertions(+), 15 deletions(-) create mode 100644 Frame 534.png create mode 100644 Frame 750.png create mode 100644 Frame 751.png create mode 100644 Vector (1).png create mode 100644 Vector (2).png create mode 100644 bi_building-check.png create mode 100644 clarity_certificate-solid.png create mode 100644 fluent_people-20-regular.png create mode 100644 healthicons_ui-user-profile-outline.png create mode 100644 hugeicons_checkmark-circle-02.png create mode 100644 ic_baseline-search.png create mode 100644 ic_outline-schedule.png create mode 100644 icons8_plus (1).png create mode 100644 icons8_plus.png create mode 100644 index3.html create mode 100644 index4.html create mode 100644 logo aidloop trans.png create mode 100644 logo aidloop transs.png create mode 100644 material-symbols-light_logout-rounded.png create mode 100644 qlementine-icons_home-24.png create mode 100644 script3.js create mode 100644 style3.css create mode 100644 uit_calendar.png diff --git a/Frame 534.png b/Frame 534.png new file mode 100644 index 0000000000000000000000000000000000000000..2366651c44033e540fcd1164781392188d101a72 GIT binary patch literal 1236 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 xpqtAL^7I|kL~BBxg$;`_R)l2QLbTOPd~Hdr ziP;jyTM-tMSrEJTCc<`vwJ=*kW>&gcFcBXsV-~C=g!oFdp8LOfr*qY->TX@ts`|eB zOKw#6z4dX=zs|X*Zs{t6*|4{__u=8;VbC#KC}&*Y->d?^5;h)>e=066 z9;>UX6M|}j&(qTrJ3chiEDK>{vDm+nNTgPerG?GS%?-lxHP`Wb6W&@` zS*b-;0OUnTdcR!Jm_1xrrcnc2$#8x$d8!;KD=T9fHGtQ0XZ^wC%eAGYrA(s+Uxw6L zlWCMh3#%wvSVhsoDvB0XQM9m%qJ>oyEv%wwVHHISnRc9W*+SZCk&9Vdv-PMcLimWp06xtE;PG znFO?oi+mg0nW;wtfNQ!oyH@c6OLs`29|{xVXrwtE#VzF4VwY4?t6>xB5SR6b$f`eaKSxMXW@bK`f`sL+i;qi6} zNN;a18y+4`8P2-B@$qpsFffp+gTYV_17e+xjg43Bt+ceX2*bO@Di~~m?ZP$yeUtMM z>j{w$;_;gLG@f$ z!nU`!vyP4qu?)jzd$16VP6QYP9p{llc-i{;IvW}q5_K@bsnYGFok3onLNh{bcDtmcszNR71R+(sAMK4CQ{1~m9~fDpiu1x zK@!)KfBT0B@FEC{v9GhU(=j=qaS)(=S65fY?aS)`7zSZg9%p4w_7lS}M9G$9GAWAJ zcs8Y&OGrjroUKkpZf8Z#hyjiDej;b)AXA1+`Gr%|a)W0-)Sg1p?1&6J_S z0P-aZ!fIb%pU9A9Ls;9%Ay^iivT|S@-{lAa0Y*nh#Xc}5yCKM;g1p^mm#6>ifH0V4 zvKwQu9xi8)49VM&Lom%w+3n9d-H6+0*pFB9vq2XXy!j?TzJL*65OhAD6Xb5eF(SG+ zi3;SK4}OU1`X+#vz(5F@K^7H2z@DBSv1}(Qpd7*vQ3?IPi5x(3h@=eR-EO#%1@}W# zpoPs5B#7yt`Cz}$|D~uXT3AKV!YYauR#CLDilT*86fLZxXkisa3#%v)_6!d*)~ErV zJkB{Y4a3;Szo=-`0FRY_d3pKAOe7M2UtV4^jT+!R0o+;F%y2lo$UXL2du`!^Ar&uw zz!S#J=H}+>WHR~j^z@WHKR+`~VFK;&(g(cJ1D{1!428MczoSBnTX>MD(2!o!^6Iw; zu~_VLC=~hyZ}QNOk}nJ}gd*BP)Mzw1M&CQVQ0Z5^CjgI>$J594gQ|lC#!?81q!;-S zhSIbzRFZ+fe1%>va)g)fm?kiAUH;=`pD+G}WiFkQ*}@8L1quX$yp6k71Q^-pReAdb8*SLvbW}dPThF17YUz!nmVC zgmUod{Yc|N`sv6om`As82D$`7{n-LppV|)dXmQD2DLA-S%H5(24J`%EPmNZ=-_1BO ztnMT;=$)-*cpJfR3wAbRO#eyX{Ys_4xK+eBPVgDQpv~yMVf?cJm{Q{RvmTjrAl)yu z^?G1(H&lv{5{@3x3S`gj3v^dFuk((U0BNc{>WK;10@YF!#WcUm=sD5YnZ$THSqvK= zs0_T*`5-Y(Iv8rsJEJY&O^(E=qt36|THyE5+(o}*e4_D4)55U-f3gyg`?`M%0IWj^ zg&yNT1;F`UtBXF;er~c!6^wXZqqJk0omL~%Frgm#Ce<#Gk+Nh+A%um|=maONz~@Wi z8`9fhzBI6~gN`QGBZVJ32Hi)<*H+I{X$-_DzKI1LntO8$U6zp6k);A)T-`mEp@X9S zj`6Ih04Q%#r_jt9iMN(}Qi4tF{{%7ieAhfO?*A3WY@LdnWK62f7U`lw7*idK7;Cmu zIq3$!N0hy2Bi%23)GRFk(&p8prlo+~L0|Nu2y1lMk+PO4Q+3b^y{mNC7w?xdx_O7a z=$>*|tyX0HJk_>De)IP=LKO2I4pR`LsP`+d(eWVU?j%owh7E1?=JZGO+xQQagOuJ& f2HFM#y*2y=ligLC!*)yr00000NkvXXu0mjf*?n*$ literal 0 HcmV?d00001 diff --git a/Vector (2).png b/Vector (2).png new file mode 100644 index 0000000000000000000000000000000000000000..71f550b1c202d95d1c9371f10be4394543c52c9c GIT binary patch literal 456 zcmV;(0XP1MP)7R;HmFBn1iC>tfDOtB=>*LNjF5JM zumSA^WP_v=s1}i%XUBlayVISl&y%oZh$3L-%F4mY$TtjDIx8iuL4tzhIS8SdBdH?^ zCo8?mIq;2lWMT+{B(aJVYuN8t^a2CWxufOJH+o-wyjJ<|gc6`+?o-cGSA3n0LrIB{ zP9U$zt<9+Kl4aR9rK1PE6e8Ef4{gH<^rsvS&$JB%9&%td-YC~$K>rn@FEF%}jvDk& z_pTU5Y1SBQ%o{ExIu47gKTk4^9-Rf>Kh{pWEmdT3k3q~nR_TMcqYO!2`7w;F*N~|) zP_P#k?`o5(7rL&Aoa-R2w=d#Fx)~ysb~Jz4$4Gn|bL?pW93M<0jus39C4l|k2v66; yw}l)zVHttU4ai%_O(c*f|D+BL6+H;nUHSt6y1Hp6!0vni0000rvw{5MG%3kFSDLDuC<2RB)6KDgY|LQ~(_i zDj=!Az6yW}?5luK!QSpz_AH9lmK+D?IKG*kDDtP5wJ*s49YDkjk`kv!Niv5a;D)$= zNVWMMcC6(W>>n<*2j4(PBFa}JOPB^{B)7JlXr!)%NURSI4l0-cq)zJ)-XVMvgziWS zdPBUu*CZ#-qjR}y#Jhl6EhHX@vk)8uwc1FP;Xba27qk;06V8RVn2UPNn=J(=>F_*> zGKR#ZGG3w0cZg~kr9M&0E{>od2}I)4*vNmqkV>g9MR{$-*Y-{Pe}T9$R786%_Bv#A zTr{GZC%q7yHF17YaqcuM1~&?i>tsMYfu6oX z@f8sF4MP^PZC3g=VqYzWc&!E0-pGhLo=}^yvS%VYxq;1qc;^H}=FDI-jbFf6z^{Nf z4gQ6NaGC?kzku5nUQ>Vvxm?L=ROv*AUez z`s!_}UU@j2qW@|BV0~?LPsK;jlTiug+*6w;o0zs3a?~xTlNoHDN1h$t3^#AjTMpLg zd=7dB+y!ovQy-_jSGl<3$j&=~+aquKnuDmH_2tcvjm)j9N|P@fF&TB+tyJlsnh4;lA4Nv=h2n;WPEF*rk+_;Ml wtgkCW2H0vLb}VDZTar|wIhPBp9fZ1ozdFsR|EafiqW}N^07*qoM6N<$g2N2X0RR91 literal 0 HcmV?d00001 diff --git a/clarity_certificate-solid.png b/clarity_certificate-solid.png new file mode 100644 index 0000000000000000000000000000000000000000..9c3708f02745501be58124689a2efd3c0ded1151 GIT binary patch literal 816 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 FV1n0r$dLd5 literal 0 HcmV?d00001 diff --git a/hugeicons_checkmark-circle-02.png b/hugeicons_checkmark-circle-02.png new file mode 100644 index 0000000000000000000000000000000000000000..0730e254608f575fa35b17de1d3296e77de0b21f GIT binary patch literal 1544 zcmV+j2KV`iP)_C#~&RK0$edDkq41g2pGXdV<(* zosNI#R?YM+>IovB!1f8OoS^Crm=j2iJyTCaa}F8=3^295yNEqW@iKqqZuLxjL)1n6ea=59&(Zt`G*rsUtJ?{~PANIC}umaT4O@~NzNmBc{u7J+ZZrSrwj>vE0 zwsM?neu#w-qd11cRSD#wEnOTt*EtE5R<(zTSV)i>{XOk%AHW)*j{fs$lTO)B6C$(w z5Sisf^6DgLQiQ`g)};*G<1wk4^OnAqY~F;_?8P!QD<)zNw(}2g5|VbtZ{C^N;QC{7 zsI-YGnP7TDWC{#`ES$(#C#J7j5|2be!+Z;)L-vz5Tay)5iJ0Apj5O{C2|WAs`?e2H z06H^$Iy0}6He$3J_I8H(adwGB&PDPl1O!h-LL<~hMc^o1k7Jd}0ulH4V;7i8Njews z#K1V_D%R1FMSjcz5%SL5F%9ng`T<>CXjQ`dc+9{il`Ii5w}Qn_Fao@~qEBQ(n6W6rP-t-t_gtr1l$2FgxHu(snm=+K?hc zgsTOCPCKFXFm-edIu3x4WWRO_hpEX6x#EbKwiR{kSC#~zu{zjMgig>YqWXK zoQO*?74xDH-iB*&esm`G^CYxoEDHNMLIr$Ht!KUe=eflg408N7|qK=~HZHnD0k(hxjX}ZCzxS^m9^x~B4BE+Mp zw{y8{+$@T||L`mTUCBoYtQItufl}&1=MH2#FWi%l`_@60%#un%zcd|y+KC9U4J9D> z+X#aNGPd+M#=}p&=Z#xL$(~Y?A-2lA?8tCWAK3IgMn>l|Br)KUpeTC!=XM`zK=Apb z2$JX2{HlnH+RKLkHfSB!2i8VLZ8`5qpXau|kyOApYx3rE#^HSPC0~>|gfpnktivfn z5gFRKG4$?Ko8YW?0DC z73BsjCmB-d=gBif4a!;G(VyYM8iV|h1tJS+CCH@- z&tqWbR1NeQv=s>Lo}EiYh_-ut^JHX97Rk;AhH}p~&Pv2cg=~NN3mz24Qfov$cQG~R zH=EWNtuSZ|RqW@ST;C*Lj|KV`-skfMCX1vs6%8+rekqADHLcX!x5(yQKZazqB=`+L znJjW?7;nOsJQ)fUnKlU;mR2OiSJG%3~58xZVa(o8tIgTA|v7G7v0000rvZ45XW~|ZlzQt4!cMzxms34{TrvjVDOi1+D>=}E;I$LMicLrxhzZnMWB%k!x?%h5NP*PG- z@;`xK!eQHY7GXy??gjz{1mO23?W9Dv0O@sHTfc@EU@9T*QM-cZobty?fy-NhMEy@3 zUBcJ(7R-prgs=s?6_*9jAxut-|Ekg1Y6E6KaUq}Ef#*3_{3!qf5HbAwWu*$1DJDcG z0--|$H$c>+6kEa}ABe9*tnk^5oEaNznFFxMcp+VPa(;p;LJCOfeA!Lwop8M~>Z=Q3 zJ4j*?vZ%eG8(X%_SRvl={dfMi57~!k3K8!D()W0%ceBSgQl17cPVT3U#GdQ^P{)Kw8(;ygkeE|+)hfJo z)~9i?ZWfJ{%@#Pyo1{F0=(k@^4%0MI3m~6e?CQ9nQyk zZioSlp?^K1XxOL}3rF_2wsy#wYwiu7-#P|55z?JFXWNzQu75qx85VX^4TXhBSWM_R z$vd^C7)!T7LDfZ(Y$dhT3pLlWA!h1MqkHsF;TW)w0^bwmrZ2brkQZu$g55M^HywI# zlZJ#@%e03VK5kM>KF6Iv9*Z}kxdc&5R}$M6V@1fc*c9`sLL|1tyt)vH4KZ^eL}Dst zZiGlo#LSftNuC(yK=+fxL;>+7kU>|?AY_|%$g&JD7Q?}zQ# hb#F>aN=iyh`3JcCaG03~6XgH^002ovPDHLkV1hBtyb1sS literal 0 HcmV?d00001 diff --git a/ic_outline-schedule.png b/ic_outline-schedule.png new file mode 100644 index 0000000000000000000000000000000000000000..d265bdb361215ac5a45c8d70006fbda71864f3bd GIT binary patch literal 1345 zcmV-H1-|-;P)Wd*kX z`e8Rzt9JTlc?D8dP-g{^R#0^XVOAjJ@sY6z+*~kHLV^GZl2ZJmGo!Hy;lV>3-WLS0 z#~ynWfx#B>AY25iJLq}bKW2adA_lZVXT4NJi!G7}papTm0E zR+di0UT7iYOs1R&$*I-m|A9_INl3C&*BvNB>*_VsPPOLF!J%i}6*Cdq4bUV7%C^^VRCN&#yHjNm(`|_COc@_je{|*#$8`v)>{4rbm+O-o#W2crFjxo|B+d+Rpqb9T3PjgYwKGsli^;O)9=2x~ zRcawz5f6_-mABBGX@?rnXG~0R>D(k%p-LjqT*ommZ!`V!$(%QFI3 zxzV_cDkQL}-X}*y9Ee-3spDTRde9JJczApF$Key3+-!+cvbH9zmM`Xdzr3`bi6n|sG3?&L55U?) z>nXCzQeT+*TRppJSLj_C@7hBr__{{sAtoiLf-Kb@qR>kZ%EDyPNh&M5Bx9*+mK8oq zD~q>Q5XV^=#R!MD88Z^dSyf9Y(YJ?g^Q2xSiURquk%TJYvE`K~Ivke#9-5<(&=v8w3xxZ?1cs?szeutD>4_7svI48;7z zquLlUmgEpj{b(ThhpDJK+8mO$DP3rK^Ly;EhdaIiT2DPHPic-?00000NkvXXu0mjf D`b1rY literal 0 HcmV?d00001 diff --git a/icons8_plus (1).png b/icons8_plus (1).png new file mode 100644 index 0000000000000000000000000000000000000000..0b8cd0b24c0182a79e2b25f8733cea37617cdd68 GIT binary patch literal 812 zcmV+{1JnG8P)LECn%kO z-URd}fKE_0NNxhyK&QNjRv6jD#Kw7TzW4O-Gfre#vit)8Jv}`x1`r8E%;+OCs`rDz zU<+M_I-Zj}k-X!1-nnfKU4S|=BzYj&;pG%06G#OqpEfX{G^9HQk+O_rZRyO0WQO(p zusuJjI?Qb~R<7qm88PCKlT==rlMKNV9+Vf(X$v-=^8;uSdnSO3(ze3;_XQz z8me&f&WxHjGVlbCGtnG8!FuPgYx0g8$9o<`c|n`K60QvC+|$RisG8c^s)k^_naf1g zGo)i7%0sklG;JQa7v;AU-Z;Qs*@&upt)0z~J5hdt^!yd&JFS7u5Sd}Ra1eSGWi|Vq z7-FW$E}ZDh(562AFx%WJ&&ga}f&-p-za33v7>uwuqH;(U1_)I`QN&d+%Z=oF- z$)sW15m>^4d4m?*z!#ydN2>Yr==gpNpI+F2d(^|uMXa~&A3zJ*Zc&VPNlrraB($1j zh-=d%liKEpZ1@Y(b9P;`86snO4C#?6UY%z)L*z=DUvv%#1Ro!AQT3{|Qw`yTX)mg- zb~03uTldnoXu8&sqjLbpzBE%{opKN#!58IpwgOKA9CepMe*u?*Auj;@bx#f56KDgT z-t(ro4)Tr$?WJGig3j{tC}F0A(+)!?8H+JX40nmWQjCb>c_qvaJmF>cUbbJn3XDA$ zEu(i}jb~CVKR-kFk3|_=3+`}%ZVE$OR9;E9gJ@{bIRoaHa6fkh)~on}WZR(gTW*^} qAY%SupqeMX3Eqi*tGyegcLV3+Z&pn#}0000LECn%kO z-URd}fKE_0NNxhyK&QNjRv6jD#Kw7TzW4O-Gfre#vit)8Jv}`x1`r8E%;+OCs`rDz zU<+M_I-Zj}k-X!1-nnfKU4S|=BzYj&;pG%06G#OqpEfX{G^9HQk+O_rZRyO0WQO(p zusuJjI?Qb~R<7qm88PCKlT==rlMKNV9+Vf(X$v-=^8;uSdnSO3(ze3;_XQz z8me&f&WxHjGVlbCGtnG8!FuPgYx0g8$9o<`c|n`K60QvC+|$RisG8c^s)k^_naf1g zGo)i7%0sklG;JQa7v;AU-Z;Qs*@&upt)0z~J5hdt^!yd&JFS7u5Sd}Ra1eSGWi|Vq z7-FW$E}ZDh(562AFx%WJ&&ga}f&-p-za33v7>uwuqH;(U1_)I`QN&d+%Z=oF- z$)sW15m>^4d4m?*z!#ydN2>Yr==gpNpI+F2d(^|uMXa~&A3zJ*Zc&VPNlrraB($1j zh-=d%liKEpZ1@Y(b9P;`86snO4C#?6UY%z)L*z=DUvv%#1Ro!AQT3{|Qw`yTX)mg- zb~03uTldnoXu8&sqjLbpzBE%{opKN#!58IpwgOKA9CepMe*u?*Auj;@bx#f56KDgT z-t(ro4)Tr$?WJGig3j{tC}F0A(+)!?8H+JX40nmWQjCb>c_qvaJmF>cUbbJn3XDA$ zEu(i}jb~CVKR-kFk3|_=3+`}%ZVE$OR9;E9gJ@{bIRoaHa6fkh)~on}WZR(gTW*^} qAY%SupqeMX3Eqi*tGyegcLV3+Z&pn#}0000Sign Up
- +

Already have an account? Login

diff --git a/index3.html b/index3.html new file mode 100644 index 0000000..95ccbe8 --- /dev/null +++ b/index3.html @@ -0,0 +1,96 @@ + + + + + + Document + + + +

Volunteers

Manage volunteers across all events

+ + + + +
40

Total registered

33

Attended

7

No show

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameEmailstatusAttendance
John Doejohndoe@email.com

Confirmed

Mary Musamarymusa@email.com
Confirmed
David Jamesdavidjames@email.com
Confirmed
Emeka Obiemekaobi@gmail.com
Confirmed
Aisha Belloaishabello@email.com
Confirmed
Fatima Ahmedfatimaahmed@email.com
Confirmed
+ +
+
+ + + + \ No newline at end of file diff --git a/index4.html b/index4.html new file mode 100644 index 0000000..1c04a1a --- /dev/null +++ b/index4.html @@ -0,0 +1,96 @@ + + + + + + Document + + + +

Volunteers

Manage volunteers across all events

+ + + + +
40

Total registered

35

Remaining slots

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameEmailstatusAttendance
John Doejohndoe@email.com

Confirmed

Mary Musamarymusa@email.com
Confirmed
David Jamesdavidjames@email.com
Confirmed
Emeka Obiemekaobi@gmail.com
Confirmed
Aisha Belloaishabello@email.com
Confirmed
Fatima Ahmedfatimaahmed@email.com
Confirmed
+ +
+
+ + + + \ No newline at end of file diff --git a/logo aidloop trans.png b/logo aidloop trans.png new file mode 100644 index 0000000000000000000000000000000000000000..906742ad359c97c3cbdf4627260b9f38fa9eda90 GIT binary patch literal 9059 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 YE{V&3#P@^b$=?CgZ)w9yR37;M7t%xSCjbBd literal 0 HcmV?d00001 diff --git a/material-symbols-light_logout-rounded.png b/material-symbols-light_logout-rounded.png new file mode 100644 index 0000000000000000000000000000000000000000..a1269f552cdd95e1e55bdfb77333415ab1644585 GIT binary patch literal 471 zcmV;|0Vw{7P)*!R?q$tBe(qcV>V>#)JrqO8MCKP{^2h*|jpJW-X1(q$0fOIx6914oELQ zvdnMEy{GPx4%4$JX*8(_hejWi@G=ibLrWNuhT6O$T4GvKZ;*!8$nt4_hco^jy_ml(Rh|t2W9!A^l!>Mm+-@$8j9TI43d9?`RP@H5C8= N002ovPDHLkV1ft-y8HkD literal 0 HcmV?d00001 diff --git a/qlementine-icons_home-24.png b/qlementine-icons_home-24.png new file mode 100644 index 0000000000000000000000000000000000000000..e16a8ffa6c337057710b91837b1f02905a6cd314 GIT binary patch literal 659 zcmV;E0&M+>P)%9lK>D11WZKe3C37T%_CVNKH8Ig zL{U^=3{V0SvK7;_C5z!RJV3II67+&B6IPJzyibM#OGJQ71e~7miEBPj@ChD}?b8;e z_o{yw4os_*OS}lnJ15s171==7KrL{7(yuU2x z87@Vcd^sFZdzj~!G^>^!e-j&yQ3N2@`CJ*UPer?idzPY2bPlFO52J*8;xbPlFO zBzuein?y!INJNKNGc6S9R@n&!iqSFG=nxU$1_z<2!#_{Sc#;{Ns4USDU5^M~O7LL6 z$R5aUYj%%=l+M`@8!1-p_&AxR|3oRFxWQMJT?)It)25emmm?yd?w(4fZ19!e$LNSY zRLjYGC&;gFghYHJB;p$(5#I=j_=YJFxztGD9=VSD(K(nBc^B<7bNG9oSnA8exkBfs z-B{5Ic1@kEQEi0yuI>$oRqH8Y<>o2O$R~W$;LH7{qVE!y9!;_1qkflc$&4j#^@jmg tZLTtOxPP#uL^TR?s*q6}O9cYH@(UR_BeR&(V#ojh002ovPDHLkV1ge37X| { - alert('Welcome, ' + Email + '!'); + alert('Welcome, ' + email + '!'); }, 2000); } else { messageDiv.textContent = 'Invalid Email or Phone Number'; } -}) +}); // Allow Enter key to submit document.addEventListener('keypress', function(event){ if (event.key === 'Enter') { login(); } -}); \ No newline at end of file +}); diff --git a/script2.js b/script2.js index b3b6e57..be187fc 100644 --- a/script2.js +++ b/script2.js @@ -11,7 +11,7 @@ document.getElementById('form').addEventListener('submit', function(event) // Regular expression for common Nigerian phone number formats // Matches: 070/080/081/090/091 followed by 8 digits OR +234 followed by 10 digits - const nigerianPhoneRegex = /^(0[789][01]\d{8}|\+234[789][01]\d{8})$/ + const nigerianPhoneRegex = /^(0[789][01]\d{8}|\+234[789][01]\d{8})$/; messageDiv.className = 'error-message'; @@ -30,15 +30,21 @@ document.getElementById('form').addEventListener('submit', function(event) errorElement.style.display = 'none'; alert('Valid Nigerian Phone Number: ' + phoneNumber); //Here you can proceed with form submission to a server - // e.g., this.submit(); + // e.g., + this.submit(); //This will submit the form } else { // Number is invalid errorElement.style.display = 'inline'; + messageDiv.textContent ='Please enter a valid Nigerian phone number'; } }); document.addEventListener('keypress', function(event) { if (event.key === 'Enter') { - signup(); + const form = document.getElementById('form'); + // Check if form exists and is visible; + if (form && form.style.display !== 'none') { + form.dispatchEvent(new Event('submit')); + } } }); \ No newline at end of file diff --git a/script3.js b/script3.js new file mode 100644 index 0000000..ebc8ae5 --- /dev/null +++ b/script3.js @@ -0,0 +1,718 @@ +// main.js +// Complete JavaScript functionality for the Volunteer Events Platform + +// ======================== +// DATA MODELS (Simulated Database) +// ======================== + +// Sample data structure for events +let events = [ + { + id: 1, + name: "Beach Cleanup", + date: "2025-05-15", + location: "Bar Beach", + description: "Help clean the beach and protect marine life.", + capacity: 50, + registered: 32, + category: "Environment" + }, + { + id: 2, + name: "Food Bank Drive", + date: "2025-05-20", + location: "Community Center", + description: "Sort and distribute food to families in need.", + capacity: 30, + registered: 25, + category: "Community" + }, + { + id: 3, + name: "Tree Planting", + date: "2025-06-05", + location: "Ramat Park", + description: "Plant trees to restore local greenery.", + capacity: 40, + registered: 18, + category: "Environment" + }, + { + id: 4, + name: "Senior Care Visit", + date: "2025-06-10", + location: "Sunrise Senior Living", + description: "Spend time with seniors, play games, and chat.", + capacity: 20, + registered: 12, + category: "Community" + } +]; + +// Sample data for volunteers +let volunteers = [ + { id: 1, name: "John Doe", email: "johndoe@email.com", status: "Active", eventsAttended: 3 }, + { id: 2, name: "Mary Musa", email: "bob@email.com", status: "Active", eventsAttended: 5 }, + { id: 3, name: "David James", email: "davidjames@email.com", status: "Pending", eventsAttended: 1 } +]; + +// Current logged-in user (simulated) +let currentUser = { + id: 1, + name: "John Doe", + email: "john@example.com", + password: "password123", + role: "admin" // or "volunteer" +}; + +// Store registrations (eventId -> array of volunteerIds) +let eventRegistrations = { + 1: [1, 2], + 2: [1], + 3: [3], + 4: [2] +}; + +// Store certificates (volunteerId -> array of eventIds) +let certificates = [ + { volunteerId: 1, eventId: 1, date: "2025-04-01" }, + { volunteerId: 1, eventId: 2, date: "2025-04-10" }, + { volunteerId: 2, eventId: 1, date: "2025-04-01" } +]; + +// ======================== +// UTILITY FUNCTIONS +// ======================== + +// Helper to get DOM elements safely +function getElement(id) { + return document.getElementById(id); +} + +// Show/hide sections +function showSection(sectionId) { + const sections = ['dashboard-section', 'create-events-section', 'events-section', 'volunteers-section', 'certificates-section', 'profile-section', 'auth-section']; + sections.forEach(section => { + const el = getElement(section); + if (el) el.style.display = 'none'; + }); + const activeSection = getElement(sectionId); + if (activeSection) activeSection.style.display = 'block'; +} + +// Simple notification system +function showNotification(message, type = 'info') { + // Remove existing notification if any + const existing = document.querySelector('.notification'); + if (existing) existing.remove(); + + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + notification.textContent = message; + notification.style.position = 'fixed'; + notification.style.bottom = '20px'; + notification.style.right = '20px'; + notification.style.padding = '12px 24px'; + notification.style.backgroundColor = type === 'error' ? '#f44336' : '#4CAF50'; + notification.style.color = 'white'; + notification.style.borderRadius = '4px'; + notification.style.zIndex = '1000'; + notification.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)'; + document.body.appendChild(notification); + + setTimeout(() => { + notification.remove(); + }, 3000); +} + +// ======================== +// AUTHENTICATION & PROFILE +// ======================== + +// Handle login +function login(email, password) { + // Simple validation - in real app, check against database + if (email === currentUser.email && password === currentUser.password) { + currentUser = { ...currentUser, isLoggedIn: true }; + localStorage.setItem('currentUser', JSON.stringify(currentUser)); + showNotification('Login successful!'); + updateUIForAuth(); + return true; + } else { + showNotification('Invalid email or password', 'error'); + return false; + } +} + +// Handle signup +function signup(name, email, password) { + // Simple validation + if (name && email && password) { + currentUser = { + id: Date.now(), + name: name, + email: email, + password: password, + role: 'volunteer', + isLoggedIn: true + }; + localStorage.setItem('currentUser', JSON.stringify(currentUser)); + showNotification('Account created successfully!'); + updateUIForAuth(); + return true; + } else { + showNotification('Please fill all fields', 'error'); + return false; + } +} + +// Handle logout +function logout() { + currentUser = { isLoggedIn: false }; + localStorage.removeItem('currentUser'); + showNotification('Logged out successfully'); + updateUIForAuth(); +} + +// Update UI based on auth state +function updateUIForAuth() { + const authState = currentUser && currentUser.isLoggedIn; + const navLinks = document.querySelector('.NavLinks'); + const headerContainer = document.querySelector('.header-container'); + + if (authState) { + // Show main content, hide auth forms + if (getElement('auth-section')) getElement('auth-section').style.display = 'none'; + showSection('dashboard-section'); + + // Update nav to show user name + if (navLinks) { + const profileLink = Array.from(navLinks.querySelectorAll('a')).find(a => a.textContent === 'Profile'); + if (profileLink) { + profileLink.textContent = `👤 ${currentUser.name}`; + } + } + + // Load all data + loadDashboardData(); + loadEventsList(); + loadVolunteersList(); + loadCertificatesList(); + loadProfileData(); + } else { + // Show auth section + if (getElement('auth-section')) getElement('auth-section').style.display = 'block'; + const sections = ['dashboard-section', 'create-events-section', 'events-section', 'volunteers-section', 'certificates-section', 'profile-section']; + sections.forEach(section => { + const el = getElement(section); + if (el) el.style.display = 'none'; + }); + + // Reset nav profile text + if (navLinks) { + const profileLink = Array.from(navLinks.querySelectorAll('a')).find(a => a.textContent.includes('👤')); + if (profileLink) { + profileLink.textContent = 'Profile'; + } + } + } +} + +// Load profile data +function loadProfileData() { + if (!currentUser || !currentUser.isLoggedIn) return; + + const profileInfo = getElement('profile-info'); + if (profileInfo) { + profileInfo.innerHTML = ` +
+

Personal Information

+

Name: ${currentUser.name}

+

Email: ${currentUser.email}

+

Role: ${currentUser.role || 'Volunteer'}

+

Member Since: ${new Date().toLocaleDateString()}

+ +
+ `; + } + + const editForm = getElement('edit-profile-form'); + if (editForm) { + editForm.innerHTML = ` +

Edit Profile

+
+ + +
+
+ + +
+
+ + +
+ + + `; + } +} + +// Toggle edit profile form +function toggleEditProfile() { + const editForm = getElement('edit-profile-form'); + if (editForm) { + editForm.style.display = editForm.style.display === 'none' ? 'block' : 'none'; + } +} + +// Update profile +function updateProfile() { + const newName = getElement('edit-name')?.value; + const newEmail = getElement('edit-email')?.value; + const newPassword = getElement('edit-password')?.value; + + if (newName) currentUser.name = newName; + if (newEmail) currentUser.email = newEmail; + if (newPassword) currentUser.password = newPassword; + + localStorage.setItem('currentUser', JSON.stringify(currentUser)); + showNotification('Profile updated successfully'); + loadProfileData(); + updateUIForAuth(); // Refresh nav +} + +// Delete account +function deleteAccount() { + if (confirm('Are you sure you want to delete your account? This action cannot be undone.')) { + logout(); + showNotification('Account deleted'); + } +} + +// ======================== +// DASHBOARD +// ======================== + +function loadDashboardData() { + if (!currentUser || !currentUser.isLoggedIn) return; + + const upcomingEvents = events.filter(e => new Date(e.date) > new Date()); + const myEvents = eventRegistrations[currentUser.id] || []; + const myEventsCount = myEvents.length; + const certificatesCount = certificates.filter(c => c.volunteerId === currentUser.id).length; + + const dashboardStats = getElement('dashboard-stats'); + if (dashboardStats) { + dashboardStats.innerHTML = ` +
+

Total Events

+

${events.length}

+
+
+

Upcoming Events

+

${upcomingEvents.length}

+
+
+

My Registrations

+

${myEventsCount}

+
+
+

Certificates Earned

+

${certificatesCount}

+
+ `; + } + + const upcomingList = getElement('upcoming-events-list'); + if (upcomingList) { + upcomingList.innerHTML = upcomingEvents.slice(0, 3).map(event => ` +
+ ${event.name} - ${event.date} at ${event.location} + +
+ `).join(''); + } +} + +// ======================== +// EVENTS MANAGEMENT +// ======================== + +function loadEventsList() { + const eventsContainer = getElement('events-list'); + if (!eventsContainer) return; + + eventsContainer.innerHTML = events.map(event => { + const isRegistered = eventRegistrations[event.id]?.includes(currentUser?.id); + const spotsLeft = event.capacity - event.registered; + + return ` +
+

${event.name}

+

Date: ${event.date}

+

Location: ${event.location}

+

Category: ${event.category}

+

${event.description}

+

Spots Left: ${spotsLeft} / ${event.capacity}

+ ${!isRegistered ? + `` : + `` + } + ${currentUser?.role === 'admin' ? + `` : '' + } +
+ `; + }).join(''); +} + +function registerForEvent(eventId) { + if (!currentUser || !currentUser.isLoggedIn) { + showNotification('Please login to register', 'error'); + return; + } + + const event = events.find(e => e.id === eventId); + if (!event) return; + + if (event.registered >= event.capacity) { + showNotification('Event is full!', 'error'); + return; + } + + // Check if already registered + if (eventRegistrations[eventId]?.includes(currentUser.id)) { + showNotification('Already registered for this event', 'error'); + return; + } + + // Register user + if (!eventRegistrations[eventId]) eventRegistrations[eventId] = []; + eventRegistrations[eventId].push(currentUser.id); + event.registered++; + + showNotification(`Successfully registered for ${event.name}!`); + loadEventsList(); + loadDashboardData(); + loadVolunteersList(); // Refresh volunteer list +} + +function createEvent(eventData) { + const newEvent = { + id: events.length + 1, + name: eventData.name, + date: eventData.date, + location: eventData.location, + description: eventData.description, + capacity: parseInt(eventData.capacity), + registered: 0, + category: eventData.category + }; + + events.push(newEvent); + showNotification('Event created successfully!'); + loadEventsList(); + loadDashboardData(); +} + +function deleteEvent(eventId) { + if (confirm('Are you sure you want to delete this event?')) { + events = events.filter(e => e.id !== eventId); + delete eventRegistrations[eventId]; + showNotification('Event deleted'); + loadEventsList(); + loadDashboardData(); + loadVolunteersList(); + } +} + +// ======================== +// VOLUNTEERS MANAGEMENT +// ======================== + +function loadVolunteersList() { + const volunteersContainer = getElement('volunteers-list'); + if (!volunteersContainer) return; + + // Get volunteers with their event counts + const volunteersWithStats = volunteers.map(vol => ({ + ...vol, + eventCount: Object.values(eventRegistrations).flat().filter(id => id === vol.id).length + })); + + volunteersContainer.innerHTML = ` + + + + + + ${volunteersWithStats.map(vol => ` + + + + + + + + `).join('')} + +
NameEmailStatusEvents AttendedActions
${vol.name}${vol.email}${vol.status}${vol.eventCount} + + ${currentUser?.role === 'admin' ? `` : ''} +
+ `; +} + +function viewVolunteerDetails(volunteerId) { + const volunteer = volunteers.find(v => v.id === volunteerId); + if (!volunteer) return; + + // Find events this volunteer registered for + const registeredEvents = []; + for (const [eventId, regs] of Object.entries(eventRegistrations)) { + if (regs.includes(volunteerId)) { + const event = events.find(e => e.id === parseInt(eventId)); + if (event) registeredEvents.push(event); + } + } + + alert(`Volunteer: ${volunteer.name}\nEmail: ${volunteer.email}\nStatus: ${volunteer.status}\nEvents: ${registeredEvents.map(e => e.name).join(', ') || 'None'}`); +} + +function deleteVolunteer(volunteerId) { + if (confirm('Delete this volunteer?')) { + volunteers = volunteers.filter(v => v.id !== volunteerId); + // Remove from registrations + for (const eventId in eventRegistrations) { + eventRegistrations[eventId] = eventRegistrations[eventId].filter(id => id !== volunteerId); + } + showNotification('Volunteer deleted'); + loadVolunteersList(); + loadEventsList(); // Update event counts + } +} + +// ======================== +// CERTIFICATES MANAGEMENT +// ======================== + +function loadCertificatesList() { + const certificatesContainer = getElement('certificates-list'); + if (!certificatesContainer) return; + + // Get certificates for current user or all if admin + const userCerts = currentUser?.role === 'admin' ? + certificates : + certificates.filter(c => c.volunteerId === currentUser?.id); + + const certsWithDetails = userCerts.map(cert => { + const volunteer = volunteers.find(v => v.id === cert.volunteerId); + const event = events.find(e => e.id === cert.eventId); + return { + ...cert, + volunteerName: volunteer?.name || 'Unknown', + eventName: event?.name || 'Unknown Event' + }; + }); + + if (certsWithDetails.length === 0) { + certificatesContainer.innerHTML = '

No certificates yet. Complete events to earn certificates!

'; + return; + } + + certificatesContainer.innerHTML = ` + + + + + + ${certsWithDetails.map(cert => ` + + + + + + + `).join('')} + +
VolunteerEventDate EarnedActions
${cert.volunteerName}${cert.eventName}${cert.date}
+ `; +} + +function downloadCertificate(volunteerId, eventId) { + const volunteer = volunteers.find(v => v.id === volunteerId); + const event = events.find(e => e.id === eventId); + if (volunteer && event) { + // Simulate certificate download + const certContent = `Certificate of Volunteering\n\nThis certifies that ${volunteer.name} has successfully volunteered for ${event.name} on ${event.date}.\n\nDate: ${new Date().toLocaleDateString()}`; + const blob = new Blob([certContent], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `certificate_${volunteer.name}_${event.name}.txt`; + a.click(); + URL.revokeObjectURL(url); + showNotification('Certificate downloaded'); + } +} + + +// Setup navigation +function setupNavigation() { + const navLinks = document.querySelectorAll('.NavLinks a'); + const pageMapping = { + 'Dashboard': 'dashboard-section', + 'Create Events': 'create-events-section', + 'Events': 'events-section', + 'Volunteers': 'volunteers-section', + 'Certificates': 'certificates-section', + 'Profile': 'profile-section', + 'Log Out': 'logout' + }; + + navLinks.forEach(link => { + link.addEventListener('click', (e) => { + const text = link.textContent.replace('👤', '').trim(); + + if (text === 'Log Out') { + e.preventDefault(); + logout(); + return; + } + + if (pageMapping[text]) { + e.preventDefault(); + if (currentUser && currentUser.isLoggedIn) { + showSection(pageMapping[text]); + // Refresh data when navigating + if (pageMapping[text] === 'dashboard-section') loadDashboardData(); + if (pageMapping[text] === 'events-section') loadEventsList(); + if (pageMapping[text] === 'volunteers-section') loadVolunteersList(); + if (pageMapping[text] === 'certificates-section') loadCertificatesList(); + if (pageMapping[text] === 'profile-section') loadProfileData(); + } else { + showNotification('Please login first', 'error'); + } + } + }); + }); +} + +// ======================== +// INITIALIZATION +// ======================== + +function init() { + // Check for stored user + const storedUser = localStorage.getItem('currentUser'); + if (storedUser) { + currentUser = JSON.parse(storedUser); + } + + // Create missing sections in HTML if needed + const mainContainer = document.querySelector('body'); + const sections = ['dashboard-section', 'create-events-section', 'events-section', 'volunteers-section', 'certificates-section', 'profile-section', 'auth-section']; + sections.forEach(section => { + if (!getElement(section)) { + const div = document.createElement('div'); + div.id = section; + div.style.display = 'none'; + document.body.appendChild(div); + } + }); + + // Populate sections with content + const dashboardSection = getElement('dashboard-section'); + if (dashboardSection) dashboardSection.innerHTML = '

Dashboard

Upcoming Events

'; + + const createSection = getElement('create-events-section'); + if (createSection) createSection.innerHTML = ` +

Create New Event

+
+
+
+
+
+
+
+ +
+ `; + + const eventsSection = getElement('events-section'); + if (eventsSection) eventsSection.innerHTML = '

All Events

'; + + const volunteersSection = getElement('volunteers-section'); + if (volunteersSection) volunteersSection.innerHTML = '

Volunteers

'; + + const certificatesSection = getElement('certificates-section'); + if (certificatesSection) certificatesSection.innerHTML = '

Certificates

'; + + const profileSection = getElement('profile-section'); + if (profileSection) profileSection.innerHTML = '

My Profile

'; + + const authSection = getElement('auth-section'); + if (authSection) authSection.innerHTML = ` +
+
+

Login

+
+ + + + Forgot Password? +
+
+
+

Sign Up

+
+ + + + +
+
+
+ `; + + setupEventListeners(); + setupNavigation(); + updateUIForAuth(); + + // Add basic CSS styles dynamically + const style = document.createElement('style'); + style.textContent = ` + .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } + .stat-card { background: #f5f5f5; padding: 20px; border-radius: 8px; text-align: center; } + .events-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; } + .event-card, .profile-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin-bottom: 20px; } + .data-table { width: 100%; border-collapse: collapse; background: white; } + .data-table th, .data-table td { padding: 12px; border: 1px solid #ddd; text-align: left; } + .data-table th { background: #f2f2f2; } + .btn-primary, .btn-secondary, .btn-small, .btn-danger { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; } + .btn-primary { background: #4CAF50; color: white; } + .btn-secondary { background: #2196F3; color: white; } + .btn-danger { background: #f44336; color: white; } + .btn-small { padding: 4px 8px; font-size: 12px; } + .btn-disabled { background: #ccc; cursor: not-allowed; } + .form-group { margin-bottom: 15px; } + .form-group label { display: block; margin-bottom: 5px; } + .form-group input, .form-group textarea, .form-group select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; } + .auth-container { display: flex; gap: 40px; justify-content: center; padding: 40px; } + .auth-form { background: white; padding: 30px; border-radius: 8px; width: 300px; } + .auth-form input { width: 100%; padding: 10px; margin: 10px 0; } + .auth-form button { width: 100%; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; } + .status-badge { padding: 4px 8px; border-radius: 12px; font-size: 12px; } + .status-badge.active { background: #4CAF50; color: white; } + .status-badge.pending { background: #ff9800; color: white; } + .NavLinks { list-style: none; display: flex; gap: 20px; background: #333; padding: 15px; margin: 0; } + .NavLinks a { color: white; text-decoration: none; } + body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f9f9f9; } + h2 { padding: 20px; margin: 0; } + `; + document.head.appendChild(style); +} + +// Start the application +init(); \ No newline at end of file diff --git a/style.css b/style.css index 08c7961..e2719b1 100644 --- a/style.css +++ b/style.css @@ -100,6 +100,8 @@ body { border: 2px solid #999; border-radius: 10px; cursor: pointer; + font-size: 10px; + color: #fdfdfd; } diff --git a/style3.css b/style3.css new file mode 100644 index 0000000..229ddb5 --- /dev/null +++ b/style3.css @@ -0,0 +1,209 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: "Poppins", sans-serif; + +} + +.header-container { + display: flex; + flex-direction: row; + gap: 1000px; + align-items: center; + background-color: #F7F8F9; + width: 100%; + height: fit-content; + margin-left: 120px; + padding: 20px; + margin-bottom: 60px; +} + +.header-container p { + font-size: 6px; + font-weight: 500; +} + +.icons-right { + display: flex; + flex-direction: row; + gap: 0; +} + +.icons-right img { + width: 30px; + height: 30px; + +} + +.aside { + display: flex; + flex-direction: column; + width: 130px; + background-color: #1F3A5F; + color: white; + position: absolute; + top: 0; + bottom: 0; + padding-left: 3px ; + gap: 10px; + +} + +.aside li { + list-style-type: none; + padding-bottom: 10px; + font-size: 15px; + } + + .aside-items { + display: flex; + gap: 5px; + } + + .aside a { + text-decoration: none; + color: white; + } + + #aside-icons { + width: 15px; + } + + .aside .active { + background-color: blue; + width: 200px; + + + } + +.display-cards { + margin-left: 130px; + display: flex; + flex-direction: row; + justify-content: space-around; + margin-top: 20px; + + +} + +.display-card-items { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 130px; + height: 70px; + border: 1px solid #C5CCD5; + border-radius: 6px; + text-align: center; + font-size: 12px; +} + +.numbers { + font-size: 15px; + width: 100px ; + border-bottom: 1px solid #C5CCD5; + padding: 5px 10px; + margin: 5px; + +} + +.display-card-items p { + font-size: 7px; + +} + +.display-items { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 5px; +} + +.display-items img { + width: 15px; + +} + + +.aside img { + width: 120px; + align-self: center; +} + +.events-info { + margin-left: 150px; + display: flex; + flex-direction: row; + justify-content: space-between; + +} + + +.events-info a { + padding: 20px; + text-decoration: none; + color: black; +} + +.events-info .active { + color: blue; +} + +.search { + display: flex; + flex-direction: row; +} + +.search input { + height: 20px; + border-radius: 3px; + +} + +.search img { + width: 20px; + height: 20px; + background-color: #C5CCD5; + +} + +.stat-table { + margin-left: 140px; + text-align: center; + +} + +.stat-table th { + background-color: #C5CCD5; + +} + + +.stat-table th, td{ + text-align: left; + font-size: 10px; + border-bottom: 5px; + +} + +.stat-table td { + border-bottom: 1px solid #C5CCD5 ; + width: fit-content; +} + +.stat-table div { + width: 70px; + height: 18px; + background-color: green; + border-radius: 3px; + text-align: center; + color: white; + margin-bottom: 5px; + +} \ No newline at end of file diff --git a/uit_calendar.png b/uit_calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..f754f11ec084e36fb9f994bcc52ba680bb8ab3b7 GIT binary patch literal 550 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?3oVGw3ym^DWND9BhG zy@!98a_G3L7|WX3Sx@yTFGb=K!0px`xk%5~fX!mfGz? zthagfeEqKeU9vp*Y4IcR|DLkUU5n=MCi{5Znjj~@dG)NPb+v8}tNEcI1)(~DX)@{s zt=tcjdVHK(uiTYvwZ3#^@}#0Uf$z8^^YxM9rAA0)A z;wRn3^}65Jo!s|ihthS`mIJw!&*n#Nzmux+G$Ki2l^;)F!JJZVH4EO|U1tLO-$#Tk zVV>pokKe{zN3tdH_7~&&-KoFQrngB=bj!Hvb#_KIi~03^O%o>Guy0%}U{us#lhf~0 zo%*UeCFM$o304&2=G9kxLiuS$jkMqG6U(n{blU&-Bg2htTedx{+-_4Cx?qEI@rq4{ z3gVyD-ndxUzsz_0U1k3Jom1CtaJ)P7iswI aW4vtCc5K3|qkh0>WbkzLb6Mw<&;$UVfb8=C literal 0 HcmV?d00001 From 5cf700918a2ef28fa99606633d41dde085529cb7 Mon Sep 17 00:00:00 2001 From: hamlad18 Date: Mon, 23 Mar 2026 09:12:49 -0200 Subject: [PATCH 06/21] new changes --- index3.html | 2 +- index4.html | 16 ++++++++-------- style3.css | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/index3.html b/index3.html index 95ccbe8..0e36012 100644 --- a/index3.html +++ b/index3.html @@ -14,7 +14,7 @@
  • -
  • +
  • diff --git a/index4.html b/index4.html index 1c04a1a..c0a6f2e 100644 --- a/index4.html +++ b/index4.html @@ -14,7 +14,7 @@
  • -
  • +
  • @@ -23,7 +23,7 @@ -
    40

    Total registered

    35

    Remaining slots

    +
    35

    Total registered

    18

    Remaining slots

    @@ -39,7 +39,7 @@ - + @@ -47,7 +47,7 @@ - + @@ -55,7 +55,7 @@ - + @@ -64,7 +64,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -80,7 +80,7 @@ - + diff --git a/style3.css b/style3.css index 229ddb5..ee5b9d7 100644 --- a/style3.css +++ b/style3.css @@ -73,9 +73,9 @@ body { width: 15px; } - .aside .active { + .aside #active { background-color: blue; - width: 200px; + width: 100%; } From d390388c4a2ec99093e160f9506d5dd094f12148 Mon Sep 17 00:00:00 2001 From: hamlad18 Date: Mon, 23 Mar 2026 09:19:09 -0200 Subject: [PATCH 07/21] updated changes --- index4.html | 2 +- style3.css | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/index4.html b/index4.html index c0a6f2e..3680bfc 100644 --- a/index4.html +++ b/index4.html @@ -7,7 +7,7 @@ -

    Volunteers

    Manage volunteers across all events

    +

    Volunteers

    Manage volunteers across all events

      diff --git a/style3.css b/style3.css index ee5b9d7..2d81b21 100644 --- a/style3.css +++ b/style3.css @@ -182,6 +182,7 @@ body { .stat-table th { background-color: #C5CCD5; + } From 4b7fa7c9e9bef4589f9c948461819a420e813672 Mon Sep 17 00:00:00 2001 From: Folarex10 Date: Mon, 23 Mar 2026 12:27:52 +0000 Subject: [PATCH 08/21] testing --- About.html | 112 --- Admin/certificates/certificate-preview.css | 173 +++++ Admin/certificates/certificate-preview.html | 57 ++ Admin/certificates/certificate-preview.js | 193 +++++ Admin/certificates/certificates.css | 353 +++++++++ Admin/certificates/certificates.html | 128 ++++ Admin/certificates/certificates.js | 212 ++++++ Admin/dashboard/admin-dashboard.css | 446 +++++++++++ Admin/dashboard/admin-dashboard.html | 193 +++++ Admin/dashboard/admin-dashboard.js | 331 +++++++++ Admin/events/event-details.css | 248 +++++++ Admin/events/event-details.html | 85 +++ Admin/events/event-details.js | 322 ++++++++ Admin/events/events-oversight.css | 387 ++++++++++ Admin/events/events-oversight.html | 188 +++++ Admin/events/events-oversight.js | 278 +++++++ Admin/flags/flag-details.css | 231 ++++++ Admin/flags/flag-details.html | 75 ++ Admin/flags/flag-details.js | 248 +++++++ Admin/flags/flags.css | 435 +++++++++++ Admin/flags/flags.html | 189 +++++ Admin/flags/flags.js | 364 +++++++++ Admin/login/admin-login.css | 247 +++++++ Admin/login/admin-login.html | 100 +++ Admin/login/admin-login.js | 189 +++++ Admin/organizations/organization-details.css | 190 +++++ Admin/organizations/organization-details.html | 77 ++ Admin/organizations/organization-details.js | 229 ++++++ .../organizations/organization-directory.css | 401 ++++++++++ .../organizations/organization-directory.html | 190 +++++ Admin/organizations/organization-directory.js | 280 +++++++ Admin/profile/admin-profile.css | 298 ++++++++ Admin/profile/admin-profile.html | 143 ++++ Admin/profile/admin-profile.js | 136 ++++ Admin/user/user-details.css | 234 ++++++ Admin/user/user-details.html | 56 ++ Admin/user/user-details.js | 227 ++++++ Admin/user/user-management.css | 313 ++++++++ Admin/user/user-management.html | 165 +++++ Admin/user/user-management.js | 228 ++++++ Admin/verification/verification-details.css | 190 +++++ Admin/verification/verification-details.html | 71 ++ Admin/verification/verification-details.js | 303 ++++++++ Admin/verification/verification-queue.css | 255 +++++++ Admin/verification/verification-queue.html | 114 +++ Admin/verification/verification-queue.js | 282 +++++++ HowItWorks.html | 4 +- ...WhatsApp Image 2026-03-08 at 15.32.16.jpeg | Bin 41838 -> 0 bytes LandingPageStyle.css | 84 +-- .../certificates/organizer-certificates.css | 366 +++++++++ .../certificates/organizer-certificates.html | 185 +++++ .../certificates/organizer-certificates.js | 356 +++++++++ Organizer/dashboard/organizer-dashboard.css | 321 ++++++++ Organizer/dashboard/organizer-dashboard.html | 159 ++++ Organizer/dashboard/organizer-dashboard.js | 247 +++++++ Organizer/email-verified/email-verified.css | 192 +++++ Organizer/email-verified/email-verified.html | 67 ++ Organizer/email-verified/email-verified.js | 64 ++ Organizer/events/cancel-event.css | 128 ++++ Organizer/events/cancel-event.html | 126 ++++ Organizer/events/cancel-event.js | 123 +++ Organizer/events/create-event.css | 82 ++ Organizer/events/create-event.html | 164 ++++ Organizer/events/create-event.js | 162 ++++ Organizer/events/event-details.css | 88 +++ Organizer/events/event-details.html | 136 ++++ Organizer/events/event-details.js | 161 ++++ Organizer/events/event-listing.css | 254 +++++++ Organizer/events/event-listing.html | 131 ++++ Organizer/events/event-listing.js | 226 ++++++ Organizer/login/organizer-login.css | 220 ++++++ Organizer/login/organizer-login.html | 108 +++ Organizer/login/organizer-login.js | 133 ++++ Organizer/profile/organizer-profile.css | 407 ++++++++++ Organizer/profile/organizer-profile.html | 206 ++++++ Organizer/profile/organizer-profile.js | 281 +++++++ Organizer/signup/organizer-signup.css | 139 ++++ Organizer/signup/organizer-signup.html | 119 +++ Organizer/signup/organizer-signup.js | 126 ++++ Organizer/verify-email/verify-email.css | 160 ++++ Organizer/verify-email/verify-email.html | 68 ++ Organizer/verify-email/verify-email.js | 97 +++ Organizer/volunteers/volunteers.css | 482 ++++++++++++ Organizer/volunteers/volunteers.html | 202 +++++ Organizer/volunteers/volunteers.js | 292 ++++++++ {Images => assets/Images}/AIDLoopLogo.jpeg | Bin .../Images}/LandingPageImage.jpeg | Bin assets/Images/Logo.png | Bin 0 -> 15489 bytes {Images => assets/Images}/Sign_Up.png | Bin {Images => assets/Images}/certificate.png | Bin {Images => assets/Images}/check_circle.png | Bin {Images => assets/Images}/clipboard.png | Bin {Images => assets/Images}/event.png | Bin {Images => assets/Images}/instagram.png | Bin {Images => assets/Images}/linkedin.png | Bin {Images => assets/Images}/shield_check.png | Bin {Images => assets/Images}/user.png | Bin assets/Images/volunteer.png | Bin 0 -> 2030456 bytes {Images => assets/Images}/x.png | Bin assets/css/badges.css | 30 + assets/css/forms.css | 42 ++ assets/css/layout.css | 88 +++ assets/css/shared.css | 28 + assets/css/tables.css | 34 + Contact.html => assets/js/admin-header.js | 0 assets/js/admin.js | 34 + assets/js/api.js | 34 + assets/js/auth.js | 27 + assets/js/config.js | 28 + assets/js/logout.js | 16 + assets/js/render.js | 19 + Login.html => assets/js/role-redirect.js | 0 signUp.html => assets/js/session.js | 0 assets/js/ui.js | 13 + assets/js/utils.js | 30 + index.css | 699 ++++++++++++++++++ index.html | 330 +++++++++ index.js | 148 ++++ 118 files changed, 18475 insertions(+), 157 deletions(-) delete mode 100644 About.html create mode 100644 Admin/certificates/certificate-preview.css create mode 100644 Admin/certificates/certificate-preview.html create mode 100644 Admin/certificates/certificate-preview.js create mode 100644 Admin/certificates/certificates.css create mode 100644 Admin/certificates/certificates.html create mode 100644 Admin/certificates/certificates.js create mode 100644 Admin/dashboard/admin-dashboard.css create mode 100644 Admin/dashboard/admin-dashboard.html create mode 100644 Admin/dashboard/admin-dashboard.js create mode 100644 Admin/events/event-details.css create mode 100644 Admin/events/event-details.html create mode 100644 Admin/events/event-details.js create mode 100644 Admin/events/events-oversight.css create mode 100644 Admin/events/events-oversight.html create mode 100644 Admin/events/events-oversight.js create mode 100644 Admin/flags/flag-details.css create mode 100644 Admin/flags/flag-details.html create mode 100644 Admin/flags/flag-details.js create mode 100644 Admin/flags/flags.css create mode 100644 Admin/flags/flags.html create mode 100644 Admin/flags/flags.js create mode 100644 Admin/login/admin-login.css create mode 100644 Admin/login/admin-login.html create mode 100644 Admin/login/admin-login.js create mode 100644 Admin/organizations/organization-details.css create mode 100644 Admin/organizations/organization-details.html create mode 100644 Admin/organizations/organization-details.js create mode 100644 Admin/organizations/organization-directory.css create mode 100644 Admin/organizations/organization-directory.html create mode 100644 Admin/organizations/organization-directory.js create mode 100644 Admin/profile/admin-profile.css create mode 100644 Admin/profile/admin-profile.html create mode 100644 Admin/profile/admin-profile.js create mode 100644 Admin/user/user-details.css create mode 100644 Admin/user/user-details.html create mode 100644 Admin/user/user-details.js create mode 100644 Admin/user/user-management.css create mode 100644 Admin/user/user-management.html create mode 100644 Admin/user/user-management.js create mode 100644 Admin/verification/verification-details.css create mode 100644 Admin/verification/verification-details.html create mode 100644 Admin/verification/verification-details.js create mode 100644 Admin/verification/verification-queue.css create mode 100644 Admin/verification/verification-queue.html create mode 100644 Admin/verification/verification-queue.js delete mode 100644 Images/WhatsApp Image 2026-03-08 at 15.32.16.jpeg create mode 100644 Organizer/certificates/organizer-certificates.css create mode 100644 Organizer/certificates/organizer-certificates.html create mode 100644 Organizer/certificates/organizer-certificates.js create mode 100644 Organizer/dashboard/organizer-dashboard.css create mode 100644 Organizer/dashboard/organizer-dashboard.html create mode 100644 Organizer/dashboard/organizer-dashboard.js create mode 100644 Organizer/email-verified/email-verified.css create mode 100644 Organizer/email-verified/email-verified.html create mode 100644 Organizer/email-verified/email-verified.js create mode 100644 Organizer/events/cancel-event.css create mode 100644 Organizer/events/cancel-event.html create mode 100644 Organizer/events/cancel-event.js create mode 100644 Organizer/events/create-event.css create mode 100644 Organizer/events/create-event.html create mode 100644 Organizer/events/create-event.js create mode 100644 Organizer/events/event-details.css create mode 100644 Organizer/events/event-details.html create mode 100644 Organizer/events/event-details.js create mode 100644 Organizer/events/event-listing.css create mode 100644 Organizer/events/event-listing.html create mode 100644 Organizer/events/event-listing.js create mode 100644 Organizer/login/organizer-login.css create mode 100644 Organizer/login/organizer-login.html create mode 100644 Organizer/login/organizer-login.js create mode 100644 Organizer/profile/organizer-profile.css create mode 100644 Organizer/profile/organizer-profile.html create mode 100644 Organizer/profile/organizer-profile.js create mode 100644 Organizer/signup/organizer-signup.css create mode 100644 Organizer/signup/organizer-signup.html create mode 100644 Organizer/signup/organizer-signup.js create mode 100644 Organizer/verify-email/verify-email.css create mode 100644 Organizer/verify-email/verify-email.html create mode 100644 Organizer/verify-email/verify-email.js create mode 100644 Organizer/volunteers/volunteers.css create mode 100644 Organizer/volunteers/volunteers.html create mode 100644 Organizer/volunteers/volunteers.js rename {Images => assets/Images}/AIDLoopLogo.jpeg (100%) rename {Images => assets/Images}/LandingPageImage.jpeg (100%) create mode 100644 assets/Images/Logo.png rename {Images => assets/Images}/Sign_Up.png (100%) rename {Images => assets/Images}/certificate.png (100%) rename {Images => assets/Images}/check_circle.png (100%) rename {Images => assets/Images}/clipboard.png (100%) rename {Images => assets/Images}/event.png (100%) rename {Images => assets/Images}/instagram.png (100%) rename {Images => assets/Images}/linkedin.png (100%) rename {Images => assets/Images}/shield_check.png (100%) rename {Images => assets/Images}/user.png (100%) create mode 100644 assets/Images/volunteer.png rename {Images => assets/Images}/x.png (100%) create mode 100644 assets/css/badges.css create mode 100644 assets/css/forms.css create mode 100644 assets/css/layout.css create mode 100644 assets/css/shared.css create mode 100644 assets/css/tables.css rename Contact.html => assets/js/admin-header.js (100%) create mode 100644 assets/js/admin.js create mode 100644 assets/js/api.js create mode 100644 assets/js/auth.js create mode 100644 assets/js/config.js create mode 100644 assets/js/logout.js create mode 100644 assets/js/render.js rename Login.html => assets/js/role-redirect.js (100%) rename signUp.html => assets/js/session.js (100%) create mode 100644 assets/js/ui.js create mode 100644 assets/js/utils.js create mode 100644 index.css create mode 100644 index.html create mode 100644 index.js diff --git a/About.html b/About.html deleted file mode 100644 index ba0cbbc..0000000 --- a/About.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - AIDLOOP-FRONTENDWEB - - - - -
      - - -
      - -
      - -

      Recruit Verify

      - -

      - Volun-teer -

      - -

      for Your Events

      - -

      - Create and manage volunteers, connect with verified volunteers, - track attendance, and reward effort with certificates — all in one - platform designed to maximize your community impact. -

      - -
      - - -
      - -
      - - -
      - Volunteer platform preview -
      - -
      - - -
      - - - -
      - - - - \ No newline at end of file diff --git a/Admin/certificates/certificate-preview.css b/Admin/certificates/certificate-preview.css new file mode 100644 index 0000000..42ec216 --- /dev/null +++ b/Admin/certificates/certificate-preview.css @@ -0,0 +1,173 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + background: #edf1f5; + min-height: 100vh; +} + +.overlay { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} + +.modal { + position: relative; + width: 100%; + max-width: 30.5rem; + min-height: 52rem; + background: #ffffff; + border-radius: 1.5rem; + box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.12); + padding: 2.5rem 2rem 3rem; +} + +.close-btn { + position: absolute; + top: 1.6rem; + right: 1.7rem; + border: none; + background: transparent; + color: #111827; + font-size: 1.9rem; + cursor: pointer; +} + +.modal h1 { + font-size: 2.15rem; + font-weight: 700; + color: #172033; + margin-top: 3rem; + margin-bottom: 3.2rem; +} + +.details-grid { + display: grid; + grid-template-columns: 1fr 1.2fr; + column-gap: 1.5rem; + row-gap: 2.35rem; + margin-bottom: 4.8rem; +} + +.label { + font-size: 0.95rem; + color: #172033; + font-weight: 400; +} + +.value { + font-size: 0.95rem; + color: #172033; + font-weight: 600; + word-break: break-word; +} + +.certificate-card { + width: 13.5rem; + height: 8.8rem; + margin: 0 auto; + border-radius: 1.6rem; + background: #1d7de2; + box-shadow: 0 0.4rem 0.9rem rgba(0, 0, 0, 0.18); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 0.8rem; + color: #ffffff; +} + +.certificate-icon { + position: relative; + font-size: 2.8rem; + line-height: 1; +} + +.badge-icon { + position: absolute; + bottom: -0.15rem; + right: -0.55rem; + font-size: 1.45rem; +} + +.certificate-card p { + font-size: 1.15rem; + font-weight: 700; + letter-spacing: 0.02em; +} + +.action-row { + margin-top: 1.5rem; + display: flex; + justify-content: center; +} + +.action-btn { + border: none; + border-radius: 0.7rem; + padding: 0.95rem 1.4rem; + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; +} + +.secondary-btn { + background: #223f6b; + color: #ffffff; +} + +.action-btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.feedback { + min-height: 1.2rem; + margin-top: 1rem; + text-align: center; + font-size: 0.9rem; +} + +.feedback.success { + color: #15803d; +} + +.feedback.error { + color: #dc2626; +} + +@media (max-width: 32rem) { + .modal { + min-height: auto; + padding: 2rem 1.2rem 2rem; + } + + .modal h1 { + font-size: 1.75rem; + margin-top: 2.5rem; + margin-bottom: 2rem; + } + + .details-grid { + grid-template-columns: 1fr; + row-gap: 0.7rem; + margin-bottom: 3rem; + } + + .label { + font-weight: 600; + margin-top: 0.75rem; + } + + .certificate-card { + width: 100%; + max-width: 13.5rem; + } +} \ No newline at end of file diff --git a/Admin/certificates/certificate-preview.html b/Admin/certificates/certificate-preview.html new file mode 100644 index 0000000..8c393f9 --- /dev/null +++ b/Admin/certificates/certificate-preview.html @@ -0,0 +1,57 @@ + + + + + + Certificate Preview + + + + +
      + +
      + + + + \ No newline at end of file diff --git a/Admin/certificates/certificate-preview.js b/Admin/certificates/certificate-preview.js new file mode 100644 index 0000000..bf160ba --- /dev/null +++ b/Admin/certificates/certificate-preview.js @@ -0,0 +1,193 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + overlay: document.getElementById("overlay"), + closeBtn: document.getElementById("closeBtn"), + volunteerName: document.getElementById("volunteerName"), + eventName: document.getElementById("eventName"), + phoneNumber: document.getElementById("phoneNumber"), + organizerName: document.getElementById("organizerName"), + eventDate: document.getElementById("eventDate"), + certificateStatus: document.getElementById("certificateStatus"), + feedback: document.getElementById("feedback"), + downloadBtn: document.getElementById("downloadBtn") +}; + +let certificateId = null; +let certificateRecord = null; + +function getCertificateIdFromURL() { + const params = new URLSearchParams(window.location.search); + return params.get("id"); +} + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const isJson = contentType.includes("application/json"); + const data = isJson ? await response.json() : await response.blob(); + + if (!response.ok) { + if (isJson) { + throw new Error(data.message || data.error || "Request failed"); + } + throw new Error("Request failed"); + } + + return { data, response }; +} + +function formatDate(dateValue) { + if (!dateValue) return "—"; + const date = new Date(dateValue); + if (Number.isNaN(date.getTime())) return dateValue; + + return date.toLocaleDateString("en-GB", { + day: "2-digit", + month: "long", + year: "numeric" + }); +} + +function getVolunteerName(record) { + return ( + record.volunteerName || + record.user?.fullName || + record.user?.name || + record.volunteer?.fullName || + record.volunteer?.name || + "—" + ); +} + +function getPhoneNumber(record) { + return ( + record.phoneNumber || + record.user?.phoneNumber || + record.user?.phone || + record.volunteer?.phoneNumber || + record.volunteer?.phone || + "—" + ); +} + +function getEventName(record) { + return ( + record.eventName || + record.event?.name || + record.event?.title || + "—" + ); +} + +function getOrganizerName(record) { + return ( + record.organizerName || + record.organizer?.fullName || + record.organizer?.name || + record.organizer?.organizationName || + record.event?.organizer?.fullName || + record.event?.organizer?.name || + record.event?.organizer?.organizationName || + "—" + ); +} + +function getCertificateStatus(record) { + return String(record.status || "issued").toUpperCase(); +} + +function renderCertificate(record) { + certificateRecord = record; + + elements.volunteerName.textContent = getVolunteerName(record); + elements.eventName.textContent = getEventName(record); + elements.phoneNumber.textContent = getPhoneNumber(record); + elements.organizerName.textContent = getOrganizerName(record); + elements.eventDate.textContent = formatDate( + record.eventDate || + record.event?.date || + record.issuedAt || + record.createdAt + ); + elements.certificateStatus.textContent = getCertificateStatus(record); +} + +async function loadCertificatePreview() { + certificateId = getCertificateIdFromURL(); + + if (!certificateId) { + elements.feedback.textContent = "Certificate ID not found in URL."; + elements.feedback.className = "feedback error"; + return; + } + + try { + const { data } = await apiRequest(`/certificates/verify/${certificateId}`); + renderCertificate(data); + } catch (error) { + elements.feedback.textContent = error.message; + elements.feedback.className = "feedback error"; + } +} + +async function downloadCertificate() { + if (!certificateId) return; + + try { + elements.downloadBtn.disabled = true; + elements.downloadBtn.textContent = "Downloading..."; + elements.feedback.textContent = ""; + + const { data, response } = await apiRequest( + `/certificates/download/${certificateId}`, + { + method: "GET" + } + ); + + const blobUrl = window.URL.createObjectURL(data); + const link = document.createElement("a"); + const disposition = response.headers.get("content-disposition") || ""; + const filenameMatch = disposition.match(/filename="?([^"]+)"?/i); + const filename = filenameMatch ? filenameMatch[1] : `certificate-${certificateId}.pdf`; + + link.href = blobUrl; + link.download = filename; + document.body.appendChild(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(blobUrl); + + elements.feedback.textContent = "Certificate downloaded successfully."; + elements.feedback.className = "feedback success"; + } catch (error) { + elements.feedback.textContent = error.message || "Failed to download certificate."; + elements.feedback.className = "feedback error"; + } finally { + elements.downloadBtn.disabled = false; + elements.downloadBtn.textContent = "Download Certificate"; + } +} + +function closeModal() { + window.location.href = "certificates.html"; +} + +elements.closeBtn.addEventListener("click", closeModal); +elements.downloadBtn.addEventListener("click", downloadCertificate); + +elements.overlay.addEventListener("click", (event) => { + if (event.target === elements.overlay) { + closeModal(); + } +}); + +document.addEventListener("DOMContentLoaded", loadCertificatePreview); \ No newline at end of file diff --git a/Admin/certificates/certificates.css b/Admin/certificates/certificates.css new file mode 100644 index 0000000..0e9e1d0 --- /dev/null +++ b/Admin/certificates/certificates.css @@ -0,0 +1,353 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + background: #eef3f7; + color: #172033; +} + +.dashboard { + display: flex; + min-height: 100vh; +} + +/* Sidebar */ +.sidebar { + width: 15.5rem; + background: #223f6b; + color: #ffffff; + padding: 2rem 0.9rem; +} + +.sidebar-logo { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + margin-bottom: 2.8rem; +} + +.sidebar-logo img { + width: 3.1rem; + height: 3.1rem; + object-fit: contain; +} + +.sidebar-logo h2 { + font-size: 1.25rem; + font-weight: 700; + color: #1d7de2; +} + +.sidebar-logo h2 span { + color: #27a657; +} + +.sidebar-menu { + list-style: none; +} + +.sidebar-menu li { + margin-bottom: 0.85rem; +} + +.sidebar-menu li a { + display: flex; + align-items: center; + gap: 0.95rem; + text-decoration: none; + color: #ffffff; + padding: 0.95rem 0.9rem; + border-radius: 0.45rem; + font-size: 0.98rem; + transition: background 0.2s ease; +} + +.sidebar-menu li a i { + width: 1rem; + text-align: center; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover { + background: #1d7de2; +} + +/* Main */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + +/* Topbar */ +.topbar { + height: 6rem; + background: #ffffff; + border-bottom: 0.0625rem solid #e6e8eb; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1.9rem; +} + +.search-box { + width: 24rem; + height: 3rem; + display: flex; + align-items: center; + gap: 0.8rem; + border: 0.0625rem solid #d9dde3; + border-radius: 0.4rem; + padding: 0 0.95rem; + background: #ffffff; +} + +.search-box i { + color: #6b7280; +} + +.search-box input { + border: none; + outline: none; + width: 100%; + background: transparent; + font-size: 1rem; + color: #374151; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 1.2rem; +} + +.notification { + position: relative; + color: #111827; + font-size: 1.35rem; +} + +.notification-dot { + position: absolute; + top: 0.1rem; + right: -0.12rem; + width: 0.48rem; + height: 0.48rem; + background: #ef2f2f; + border-radius: 50%; +} + +.admin-profile { + display: flex; + align-items: center; + gap: 0.8rem; +} + +.user-avatar { + width: 2.65rem; + height: 2.65rem; + border-radius: 50%; + object-fit: cover; +} + +.admin-text h4 { + font-size: 0.95rem; + font-weight: 600; + color: #172033; + line-height: 1.2; +} + +.admin-text p { + font-size: 0.83rem; + color: #6b7280; + line-height: 1.2; +} + +.admin-chevron { + color: #6b7280; + font-size: 0.78rem; +} + +/* Page */ +.page-content { + padding: 2rem 1.9rem; +} + +.page-header { + margin-bottom: 1.2rem; +} + +.page-header h1 { + font-size: 2.5rem; + line-height: 1.1; + font-weight: 700; + margin-bottom: 0.45rem; +} + +.page-header p { + font-size: 1rem; + color: #6b7280; + margin-bottom: 1.35rem; +} + +/* Filters */ +.filter-buttons { + display: flex; + gap: 0.7rem; + align-items: center; + margin-bottom: 2rem; +} + +.filter-btn { + border: 0.0625rem solid #d9dde3; + background: #ffffff; + color: #1f2937; + padding: 0.8rem 1rem; + border-radius: 0.45rem; + font-size: 0.92rem; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 0.45rem; +} + +.filter-btn.active { + background: #e8f2ff; + border-color: #e8f2ff; + color: #1d7de2; +} + +/* Table */ +.table-wrapper { + background: #ffffff; + border: 0.0625rem solid #e3e7eb; + border-radius: 0.45rem; + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #eef1f4; +} + +th, +td { + text-align: left; + padding: 1.35rem 1.2rem; +} + +th { + font-size: 0.66rem; + letter-spacing: 0.04em; + color: #7a8290; + font-weight: 700; +} + +td { + font-size: 0.96rem; + color: #172033; + border-top: 0.0625rem solid #edf0f2; + vertical-align: top; + font-weight: 500; +} + +.action-links a { + color: #63a1da; + text-decoration: none; + font-size: 0.84rem; + font-weight: 600; +} + +.action-links a:hover { + text-decoration: underline; +} + +.hidden-row { + display: none; +} + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 13.5rem; + text-align: center; + padding: 2.5rem 1rem 3rem; +} + +.empty-icon { + width: 4.5rem; + height: 4.5rem; + display: flex; + align-items: center; + justify-content: center; + color: #8b8f96; + font-size: 3rem; + margin-bottom: 1rem; +} + +.empty-state h2 { + font-size: 1.2rem; + font-weight: 700; + color: #172033; + margin-bottom: 0.65rem; +} + +.empty-state p { + font-size: 0.95rem; + color: #4b5563; +} + +@media (max-width: 75rem) { + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 60rem; + } +} + +@media (max-width: 62rem) { + .sidebar { + display: none; + } + + .topbar { + padding: 0 1rem; + } + + .search-box { + width: 16rem; + } + + .page-content { + padding: 1.5rem 1rem; + } + + .page-header h1 { + font-size: 2rem; + } + + .admin-text, + .admin-chevron { + display: none; + } +} + +@media (max-width: 36rem) { + .filter-buttons { + flex-wrap: wrap; + } +} \ No newline at end of file diff --git a/Admin/certificates/certificates.html b/Admin/certificates/certificates.html new file mode 100644 index 0000000..752bdfc --- /dev/null +++ b/Admin/certificates/certificates.html @@ -0,0 +1,128 @@ + + + + + + AIDLoop - Certificates + + + + +
      + + +
      +
      + + +
      +
      + + +
      + +
      + Admin Avatar +
      +

      Loading...

      +

      Admin

      +
      + +
      +
      +
      + +
      + + +
      + + + +
      + +
      +
    John Doe johndoe@email.com

    Confirmed

    Events not yet held
    Mary Musa marymusa@email.com
    Confirmed
    Events not yet held
    David James davidjames@email.com
    Confirmed
    Events not yet held
    Emeka Obi emekaobi@gmail.com
    Confirmed
    Events not yet held
    Aisha Bello aishabello@email.com
    Confirmed
    Events not yet held
    Fatima Ahmed fatimaahmed@email.com
    Confirmed
    Events not yet held
    + + + + + + + + + + + + + + + +
    VOLUNTEER NAMEEVENT NAMEORGANIZERDATE ISSUEDACTIONS
    Loading certificates...
    + + +
    + + + + + + + \ No newline at end of file diff --git a/Admin/certificates/certificates.js b/Admin/certificates/certificates.js new file mode 100644 index 0000000..47e0746 --- /dev/null +++ b/Admin/certificates/certificates.js @@ -0,0 +1,212 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + searchInput: document.getElementById("searchInput"), + certificatesTable: document.getElementById("certificatesTable"), + emptyState: document.getElementById("emptyState"), + adminName: document.getElementById("adminName"), + adminRole: document.getElementById("adminRole"), + adminAvatar: document.getElementById("adminAvatar"), + filterButtons: document.querySelectorAll(".filter-btn") +}; + +let allCertificates = []; +let currentFilter = "all"; + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeCertificates(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.certificates)) return payload.certificates; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function formatDate(dateValue) { + if (!dateValue) return "—"; + const date = new Date(dateValue); + if (Number.isNaN(date.getTime())) return dateValue; + + return date.toLocaleDateString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric" + }); +} + +function getVolunteerName(item) { + return ( + item.volunteerName || + item.user?.fullName || + item.user?.name || + item.volunteer?.fullName || + item.volunteer?.name || + "—" + ); +} + +function getEventName(item) { + return ( + item.eventName || + item.event?.name || + item.event?.title || + "—" + ); +} + +function getOrganizerName(item) { + return ( + item.organizerName || + item.organizer?.fullName || + item.organizer?.name || + item.organizer?.organizationName || + item.event?.organizer?.fullName || + item.event?.organizer?.name || + item.event?.organizer?.organizationName || + "—" + ); +} + +function getCertificateStatus(item) { + return String(item.status || "issued").toLowerCase(); +} + +function getCertificateId(item) { + return item.certificateId || item._id || item.id || ""; +} + +async function loadAdminProfile() { + try { + let profile; + + try { + profile = await apiRequest("/users/me"); + } catch { + profile = await apiRequest("/user/me"); + } + + elements.adminName.textContent = profile.fullName || profile.name || "Admin"; + elements.adminRole.textContent = profile.role + ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1) + : "Admin"; + + if (profile.profileImage) { + elements.adminAvatar.src = profile.profileImage; + } + } catch (error) { + console.error("Failed to load admin profile:", error.message); + window.location.href = "../login/admin-login.html"; + } +} + +async function loadCertificates() { + try { + const payload = await apiRequest("/certificates/my-certificates"); + allCertificates = normalizeCertificates(payload); + renderCertificates(); + } catch (error) { + console.error("Failed to load certificates:", error.message); + elements.certificatesTable.innerHTML = ` + + Failed to load certificates. + + `; + } +} + +function renderCertificates() { + const query = elements.searchInput.value.trim().toLowerCase(); + + const filtered = allCertificates.filter((item) => { + const status = getCertificateStatus(item); + + const matchesFilter = + currentFilter === "all" + ? true + : status === currentFilter; + + const searchable = ` + ${getVolunteerName(item)} + ${getEventName(item)} + ${getOrganizerName(item)} + ${status} + `.toLowerCase(); + + return matchesFilter && searchable.includes(query); + }); + + if (!filtered.length) { + elements.certificatesTable.innerHTML = ""; + elements.emptyState.style.display = "flex"; + return; + } + + elements.emptyState.style.display = "none"; + + elements.certificatesTable.innerHTML = filtered + .map((item) => { + const certificateId = getCertificateId(item); + const status = getCertificateStatus(item); + + return ` + + ${getVolunteerName(item)} + ${getEventName(item)} + ${getOrganizerName(item)} + ${formatDate(item.dateIssued || item.issuedAt || item.createdAt)} + + + View Certificate + + + + `; + }) + .join(""); +} + +function bindFilters() { + elements.filterButtons.forEach((button) => { + button.addEventListener("click", () => { + elements.filterButtons.forEach((btn) => btn.classList.remove("active")); + button.classList.add("active"); + currentFilter = button.dataset.filter; + renderCertificates(); + }); + }); +} + +function bindSearch() { + elements.searchInput.addEventListener("input", renderCertificates); +} + +document.addEventListener("DOMContentLoaded", async () => { + bindFilters(); + bindSearch(); + await loadAdminProfile(); + await loadCertificates(); +}); \ No newline at end of file diff --git a/Admin/dashboard/admin-dashboard.css b/Admin/dashboard/admin-dashboard.css new file mode 100644 index 0000000..371b4fe --- /dev/null +++ b/Admin/dashboard/admin-dashboard.css @@ -0,0 +1,446 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + background: #edf2f6; + color: #172033; +} + +.dashboard-layout { + display: flex; + min-height: 100vh; +} + + /* Sidebar */ +.sidebar { + width: 15rem; + background: #223f6b; + color: #ffffff; + padding: 1.8rem 0.9rem; +} + +.sidebar-logo { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + margin-bottom: 2.4rem; +} + +.sidebar-logo img { + width: 3.1rem; + height: 3.1rem; + object-fit: contain; +} + +.sidebar-logo h2 { + font-size: 1.25rem; + font-weight: 700; + color: #1d7de2; +} + +.sidebar-logo h2 span { + color: #27a657; +} + +.sidebar-menu { + list-style: none; +} + +.sidebar-menu li { + margin-bottom: 0.8rem; +} + +.sidebar-menu li a { + display: flex; + align-items: center; + gap: 0.85rem; + text-decoration: none; + color: #ffffff; + padding: 0.9rem 0.85rem; + border-radius: 0.45rem; + font-size: 0.95rem; + transition: background 0.2s ease; +} + +.sidebar-menu li a i { + width: 1rem; + text-align: center; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover { + background: #1d7de2; +} + + Main +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + + /* Topbar */ +.topbar { + height: 5.8rem; + background: #ffffff; + border-bottom: 0.0625rem solid #e6e8eb; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1.8rem; +} + +.search-box { + width: 21rem; + height: 2.9rem; + display: flex; + align-items: center; + gap: 0.75rem; + border: 0.0625rem solid #d9dde3; + border-radius: 0.35rem; + padding: 0 0.9rem; + background: #ffffff; +} + +.search-box i { + color: #6b7280; + font-size: 0.95rem; +} + +.search-box input { + width: 100%; + border: none; + outline: none; + background: transparent; + font-size: 0.95rem; + color: #374151; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 1rem; +} + +.notification { + position: relative; + color: #111827; + font-size: 1.2rem; +} + +.notification-dot { + position: absolute; + top: 0.08rem; + right: -0.12rem; + width: 0.45rem; + height: 0.45rem; + background: #ef2f2f; + border-radius: 50%; +} + +.admin-profile { + display: flex; + align-items: center; + gap: 0.7rem; +} + +.user-avatar { + width: 2.45rem; + height: 2.45rem; + border-radius: 50%; + object-fit: cover; +} + +.admin-text h4 { + font-size: 0.82rem; + font-weight: 600; + color: #172033; + line-height: 1.2; +} + +.admin-text p { + font-size: 0.72rem; + color: #6b7280; + line-height: 1.2; +} + +.admin-chevron { + color: #6b7280; + font-size: 0.7rem; +} + + Content +.page-content { + padding: 4rem 1.4rem 2rem; +} + +.page-header { + margin-bottom: 2rem; +} + +.page-header h1 { + font-size: 2.1rem; + font-weight: 700; + margin-bottom: 0.3rem; +} + +.page-header p { + font-size: 0.78rem; + color: #6b7280; +} + + /* Stats */ +.stats-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 1.4rem; + margin-bottom: 2.8rem; + max-width: 40rem; +} + +.stat-card { + background: #ffffff; + border: 0.0625rem solid #7f8ea5; + border-radius: 0.6rem; + overflow: hidden; + min-height: 5.8rem; +} + +.stat-value { + display: flex; + align-items: center; + justify-content: center; + height: 3rem; + font-size: 1.8rem; + font-weight: 700; + color: #111827; +} + +.stat-footer { + height: 2.8rem; + border-top: 0.0625rem solid #b8c1cd; + display: flex; + align-items: center; + gap: 0.7rem; + padding: 0 1.1rem; + font-size: 0.65rem; + color: #172033; +} + +.stat-footer i { + color: #1d7de2; + font-size: 1rem; +} + + /* Section titles */ +.quick-actions-section h2, +.recent-activity-section h2 { + font-size: 1.05rem; + font-weight: 700; + margin-bottom: 1.15rem; +} + + /* Quick actions */ +.quick-actions-section { + margin-bottom: 3rem; + max-width: 42rem; +} + +.quick-actions { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2rem; +} + +.quick-btn { + border: none; + background: #1d7de2; + color: #ffffff; + padding: 0.85rem 1rem; + border-radius: 0.2rem; + font-size: 0.65rem; + cursor: pointer; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.quick-btn:hover { + opacity: 0.92; +} + +.quick-btn:active { + transform: scale(0.98); +} + + /* Recent activity */ +.recent-activity-section { + max-width: 42rem; +} + +.table-wrapper { + background: #ffffff; + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #f4f6f8; +} + +th, +td { + text-align: left; + padding: 1rem 1.4rem; +} + +th { + font-size: 0.62rem; + letter-spacing: 0.08em; + color: #7b8492; + font-weight: 700; +} + +td { + font-size: 0.68rem; + color: #172033; + border-top: 0.0625rem solid #eef1f3; +} + +.status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 4.7rem; + padding: 0.35rem 0.7rem; + border-radius: 0.45rem; + background: #f2a10d; + color: #ffffff; + font-size: 0.58rem; + font-weight: 700; +} + +.status-badge.pending { + background: #f2a10d; + color: #fff; +} + +.status-badge.published { + background: #16a34a; + color: #fff; +} + +.status-badge.completed { + background: #2563eb; + color: #fff; +} + +/* Responsive */ +@media (max-width: 75rem) { + .stats-grid, + .quick-actions { + grid-template-columns: repeat(2, 1fr); + max-width: none; + } + + .recent-activity-section, + .quick-actions-section { + max-width: none; + } + + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 42rem; + } +} + +@media (max-width: 62rem) { + .sidebar { + display: none; + } + + .topbar { + padding: 0 1rem; + } + + .search-box { + width: 16rem; + } + + .page-content { + padding: 2rem 1rem; + } + + .admin-text, + .admin-chevron { + display: none; + } +} + +@media (max-width: 36rem) { + .stats-grid, + .quick-actions { + grid-template-columns: 1fr; + } + + .search-box { + width: 12rem; + } +} + + + + + + + + + + +/* +.stats-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0,1fr)); + gap: 1rem; + margin-bottom: 2rem; +} + +.stat-card { + background: #fff; + border: 1px solid #d8dfe8; + border-radius: 0.9rem; + overflow: hidden; +} + +.stat-number { + text-align: center; + font-size: 2rem; + font-weight: 700; + padding: 1rem 0; +} + +.stat-footer { + padding: 0.85rem 1rem; + border-top: 1px solid #d8dfe8; + color: #526176; +} + +@media (max-width: 68rem) { .stats-grid { + grid-template-columns: 1fr 1fr; +} +} + +@media (max-width: 48rem) { .stats-grid { + grid-template-columns: 1fr; +} +} */ diff --git a/Admin/dashboard/admin-dashboard.html b/Admin/dashboard/admin-dashboard.html new file mode 100644 index 0000000..6a5c572 --- /dev/null +++ b/Admin/dashboard/admin-dashboard.html @@ -0,0 +1,193 @@ + + + + + + AIDLoop Dashboard + + + + +
    + Sidebar + + + +
    + +
    + + +
    +
    + + +
    + +
    + Admin Avatar +
    +

    Loading...

    +

    Admin

    +
    + +
    +
    +
    + + +
    + + + +
    +
    +
    0
    + +
    + +
    +
    0
    + +
    + +
    +
    0
    + +
    + +
    +
    0
    + +
    +
    + + +
    +

    Quick Actions

    + +
    + + + + + +
    +
    + + +
    +

    Recent Activity

    + +
    + + + + + + + + + + + + + + + +
    ACTIVITYENTITYDATESTATUS
    Loading recent activity...
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + diff --git a/Admin/dashboard/admin-dashboard.js b/Admin/dashboard/admin-dashboard.js new file mode 100644 index 0000000..a02d6d4 --- /dev/null +++ b/Admin/dashboard/admin-dashboard.js @@ -0,0 +1,331 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const els = { + adminName: document.getElementById("adminName"), + adminRole: document.getElementById("adminRole"), + adminAvatar: document.getElementById("adminAvatar"), + organizationCount: document.getElementById("organizationCount"), + pendingCount: document.getElementById("pendingCount"), + eventsCount: document.getElementById("eventsCount"), + activeUsersCount: document.getElementById("activeUsersCount"), + activityTable: document.getElementById("activityTable"), + searchInput: document.getElementById("searchInput"), + goVerificationQueue: document.getElementById("goVerificationQueue"), + viewOrganizations: document.getElementById("viewOrganizations"), + viewEvents: document.getElementById("viewEvents") +}; + +let activityRowsCache = []; + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function formatDate(dateValue) { + if (!dateValue) return "—"; + const date = new Date(dateValue); + if (Number.isNaN(date.getTime())) return dateValue; + return date.toLocaleDateString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric" + }); +} + +function setStatValue(element, target) { + const safeTarget = Number.isFinite(Number(target)) ? Number(target) : 0; + const duration = 600; + const steps = 24; + const increment = safeTarget / steps; + let current = 0; + let step = 0; + + const timer = setInterval(() => { + step += 1; + current += increment; + + if (step >= steps) { + element.textContent = safeTarget; + clearInterval(timer); + return; + } + + element.textContent = Math.round(current); + }, duration / steps); +} + +function normalizeUsers(usersPayload) { + if (Array.isArray(usersPayload)) return usersPayload; + if (Array.isArray(usersPayload?.users)) return usersPayload.users; + if (Array.isArray(usersPayload?.data)) return usersPayload.data; + return []; +} + +function normalizeEvents(eventsPayload) { + if (Array.isArray(eventsPayload)) return eventsPayload; + if (Array.isArray(eventsPayload?.events)) return eventsPayload.events; + if (Array.isArray(eventsPayload?.data)) return eventsPayload.data; + return []; +} + +function buildRecentActivity(users, events) { + const activities = []; + + users.slice(0, 5).forEach((user) => { + const isOrganizer = String(user.role || "").toLowerCase() === "organizer"; + const isPending = + String(user.status || "").toLowerCase() === "pending" || + String(user.approvalStatus || "").toLowerCase() === "pending"; + + if (isOrganizer && isPending) { + activities.push({ + activity: "Submitted for Verification", + entity: user.fullName || user.name || user.email || "Organizer", + date: formatDate(user.createdAt || user.updatedAt), + status: "Pending" + }); + } + }); + + events.slice(0, 5).forEach((event) => { + activities.push({ + activity: "Event Created", + entity: event.name || "Untitled Event", + date: formatDate(event.createdAt || event.date), + status: event.status || "Published" + }); + }); + + return activities + .sort((a, b) => { + const aDate = new Date(a.date); + const bDate = new Date(b.date); + return bDate - aDate; + }) + .slice(0, 8); +} + +function renderRecentActivity(rows) { + activityRowsCache = rows; + applyActivitySearch(); +} + +function getStatusBadgeClass(status) { + const normalized = String(status).toLowerCase(); + + if (normalized.includes("pending")) return "pending"; + if (normalized.includes("published")) return "published"; + if (normalized.includes("completed")) return "completed"; + if (normalized.includes("approved")) return "published"; + return "pending"; +} + +function applyActivitySearch() { + const query = els.searchInput.value.trim().toLowerCase(); + const filtered = activityRowsCache.filter((row) => + `${row.activity} ${row.entity} ${row.date} ${row.status}` + .toLowerCase() + .includes(query) + ); + + if (!filtered.length) { + els.activityTable.innerHTML = ` + + No matching activity found. + + `; + return; + } + + els.activityTable.innerHTML = filtered + .map( + (row) => ` + + ${row.activity} + ${row.entity} + ${row.date} + + + ${row.status} + + + + ` + ) + .join(""); +} + +async function loadAdminProfile() { + try { + let profile; + try { + profile = await apiRequest("/users/me"); + } catch { + profile = await apiRequest("/user/me"); + } + + els.adminName.textContent = + profile.fullName || + profile.name || + "Admin User"; + + els.adminRole.textContent = + profile.role + ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1) + : "Admin"; + + if (profile.profileImage) { + els.adminAvatar.src = profile.profileImage; + } + } catch (error) { + console.error("Failed to load admin profile:", error.message); + window.location.href = "../profile/admin-profile.html"; + } +} + +async function loadDashboardData() { + try { + const [usersPayload, eventsPayload] = await Promise.all([ + apiRequest("/user").catch(() => apiRequest("/users")), + apiRequest("/events") + ]); + + const users = normalizeUsers(usersPayload); + const events = normalizeEvents(eventsPayload); + + const organizers = users.filter( + (user) => String(user.role || "").toLowerCase() === "organizer" + ); + + const pendingOrganizers = organizers.filter((user) => { + const status = String(user.status || "").toLowerCase(); + const approvalStatus = String(user.approvalStatus || "").toLowerCase(); + return status === "pending" || approvalStatus === "pending"; + }); + + const activeUsers = users.filter((user) => user.isActive !== false); + + setStatValue(els.organizationCount, organizers.length); + setStatValue(els.pendingCount, pendingOrganizers.length); + setStatValue(els.eventsCount, events.length); + setStatValue(els.activeUsersCount, activeUsers.length); + + const recentActivity = buildRecentActivity(users, events); + renderRecentActivity(recentActivity); + } catch (error) { + console.error("Failed to load dashboard data:", error.message); + els.activityTable.innerHTML = ` + + Failed to load dashboard data. + + `; + } +} + +function bindUI() { + els.goVerificationQueue.addEventListener("click", () => { + window.location.href = "../verification/verification-queue.html"; + }); + + els.viewOrganizations.addEventListener("click", () => { + window.location.href = "../organizations/organization-directory.html"; + }); + + els.viewEvents.addEventListener("click", () => { + window.location.href = "../events/events-oversight.html"; + }); + + els.searchInput.addEventListener("input", applyActivitySearch); +} + +document.addEventListener("DOMContentLoaded", async () => { + bindUI(); + await loadAdminProfile(); + await loadDashboardData(); +}); + + + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { formatDate } from "../../assets/js/utils.js"; + +// const els = { +// totalOrganizations: document.getElementById("totalOrganizations"), +// pendingVerifications: document.getElementById("pendingVerifications"), +// totalEvents: document.getElementById("totalEvents"), +// activeUsers: document.getElementById("activeUsers"), +// activityTable: document.getElementById("activityTable"), +// logoutBtn: document.getElementById("logoutBtn") +// }; + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireAdmin(); +// els.logoutBtn.addEventListener("click", () => logout(ROUTES.home)); +// try { +// const [usersPayload, eventsPayload] = await Promise.all([apiRequest("/user"), apiRequest("/events")]); +// const users = normalizeArray(usersPayload, ["users"]); +// const events = normalizeArray(eventsPayload, ["events"]); +// const organizers = users.filter((u) => String(u.role || "").toLowerCase() === "organizer"); + +// const pending = organizers.filter((u) => { +// const status = String(u.status || "").toLowerCase(); +// const approvalStatus = String(u.approvalStatus || "").toLowerCase(); +// return !(status === "verified" || approvalStatus === "approved" || u.isVerified); +// }).length; + +// els.totalOrganizations.textContent = organizers.length; +// els.pendingVerifications.textContent = pending; +// els.totalEvents.textContent = events.length; +// els.activeUsers.textContent = users.length; + +// const activityRows = [ +// ...organizers.slice(0, 2).map((o) => ({ activity: "Submitted for Verification", entity: o.fullName || o.name || "Organizer", date: o.createdAt, status: "Pending" })), +// ...events.slice(0, 2).map((e) => ({ activity: "Event Created", entity: e.name || "Event", date: e.createdAt || e.date, status: e.status || "Published" })) +// ]; + +// els.activityTable.innerHTML = activityRows.map((row) => ` +// +// ${row.activity} +// ${row.entity} +// ${formatDate(row.date, "long")} +// ${row.status} +// +// `).join("") || `No recent activity.`; +// } catch { +// els.activityTable.innerHTML = `Failed to load dashboard data.`; +// } +// }); diff --git a/Admin/events/event-details.css b/Admin/events/event-details.css new file mode 100644 index 0000000..dd88003 --- /dev/null +++ b/Admin/events/event-details.css @@ -0,0 +1,248 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + min-height: 100vh; + background: #edf1f5; +} + +.overlay { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} + +.modal-card { + width: 100%; + max-width: 31rem; + background: #ffffff; + border-radius: 1.5rem; + box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.12); + padding: 5rem 1.8rem 2rem; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 3rem; + padding: 0 0.9rem; +} + +.modal-header h1 { + font-size: 1.05rem; + font-weight: 500; + color: #172033; + line-height: 1.2; +} + +.status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 8.1rem; + padding: 0.95rem 1.2rem; + border-radius: 1rem; + font-size: 0.95rem; + font-weight: 700; + color: #ffffff; +} + +.status-badge.published { + background: #16a357; +} + +.status-badge.cancelled { + background: #ef2b2b; +} + +.status-badge.flagged { + background: #dc2626; +} + +.details-grid { + display: grid; + grid-template-columns: 1fr 1.2fr; + column-gap: 1.2rem; + row-gap: 2.2rem; + padding: 0 0.9rem; + margin-bottom: 3rem; +} + +.label { + font-size: 0.95rem; + font-weight: 400; + color: #2a3342; +} + +.value { + font-size: 0.95rem; + font-weight: 500; + color: #172033; + line-height: 1.3; + word-break: break-word; +} + +.value a { + color: #172033; + text-decoration: underline; +} + +.description-section { + padding: 0 0.15rem; + margin-bottom: 1rem; +} + +.description-section h2 { + font-size: 1.05rem; + font-weight: 400; + color: #172033; + margin-bottom: 1rem; +} + +.description-box { + border: 0.08rem solid #7a8494; + border-radius: 1.4rem; + padding: 1.2rem 1.25rem; + min-height: 7.8rem; + color: #8e8f95; + font-size: 0.95rem; + line-height: 1.32; +} + +.feedback { + min-height: 1.2rem; + margin-bottom: 1.2rem; + text-align: center; + font-size: 0.9rem; +} + +.feedback.success { + color: #15803d; +} + +.feedback.error { + color: #dc2626; +} + +.action-row { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0 0.15rem; +} + +.action-btn { + border: none; + border-radius: 0.55rem; + min-width: 9rem; + padding: 1rem 1.2rem; + font-size: 0.95rem; + font-weight: 500; + color: #ffffff; + cursor: pointer; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.action-btn:hover { + opacity: 0.92; +} + +.action-btn:active { + transform: scale(0.98); +} + +.action-btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.close-btn { + background: #ef2b2b; +} + +.flag-btn { + background: #16a357; +} + +@media (max-width: 32rem) { + .modal-card { + padding: 2.5rem 1rem 1.3rem; + } + + .modal-header { + flex-direction: column; + align-items: flex-start; + padding: 0; + margin-bottom: 2rem; + } + + .status-badge { + width: 100%; + min-width: auto; + } + + .details-grid { + grid-template-columns: 1fr; + row-gap: 0.75rem; + padding: 0; + margin-bottom: 2.5rem; + } + + .label { + font-weight: 600; + margin-top: 0.75rem; + } + + .description-section { + padding: 0; + } + + .action-row { + flex-direction: column; + padding: 0; + } + + .action-btn { + width: 100%; + } +} + + + + + + + + + + + +/* .auth-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.auth-card { + width: 100%; + max-width: 32rem; + background: #fff; + padding: 2rem; + border-radius: 1rem; + box-shadow: 0 0.5rem 2rem rgba(0,0,0,0.06); +} + +#feedback { + display: block; + margin-top: 0.75rem; +} */ diff --git a/Admin/events/event-details.html b/Admin/events/event-details.html new file mode 100644 index 0000000..8139f76 --- /dev/null +++ b/Admin/events/event-details.html @@ -0,0 +1,85 @@ + + + + + + Event Details + + + +
    + +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/events/event-details.js b/Admin/events/event-details.js new file mode 100644 index 0000000..100496d --- /dev/null +++ b/Admin/events/event-details.js @@ -0,0 +1,322 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + overlay: document.getElementById("overlay"), + closeBtn: document.getElementById("closeBtn"), + flagBtn: document.getElementById("flagBtn"), + eventTitle: document.getElementById("eventTitle"), + orgName: document.getElementById("orgName"), + socialLinks: document.getElementById("socialLinks"), + email: document.getElementById("email"), + phoneNumber: document.getElementById("phoneNumber"), + dateTime: document.getElementById("dateTime"), + slotsFilled: document.getElementById("slotsFilled"), + description: document.getElementById("description"), + statusBadge: document.getElementById("statusBadge"), + feedback: document.getElementById("feedback") +}; + +let eventId = null; +let currentEvent = null; + +function getEventIdFromURL() { + const params = new URLSearchParams(window.location.search); + return params.get("id"); +} + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function formatDate(dateValue) { + if (!dateValue) return "—"; + const date = new Date(dateValue); + if (Number.isNaN(date.getTime())) return dateValue; + + return date.toLocaleDateString("en-GB", { + day: "2-digit", + month: "long", + year: "numeric" + }); +} + +function getEventStatus(event) { + const status = String(event.status || "").toLowerCase(); + if (status === "cancelled" || status === "canceled") return "cancelled"; + if (status === "flagged") return "flagged"; + return "published"; +} + +function setStatusBadge(status) { + elements.statusBadge.className = `status-badge ${status}`; + + if (status === "cancelled") { + elements.statusBadge.textContent = "Cancelled"; + return; + } + + if (status === "flagged") { + elements.statusBadge.textContent = "Flagged"; + return; + } + + elements.statusBadge.textContent = "Published"; +} + +function getOrganizerName(event) { + if (typeof event.organizer === "object" && event.organizer) { + return ( + event.organizer.fullName || + event.organizer.name || + event.organizer.organizationName || + "Organizer" + ); + } + + return event.organizerName || "Organizer"; +} + +function getOrganizerEmail(event) { + if (typeof event.organizer === "object" && event.organizer) { + return event.organizer.email || "—"; + } + + return event.contactEmail || event.email || "—"; +} + +function getOrganizerPhone(event) { + if (typeof event.organizer === "object" && event.organizer) { + return event.organizer.phoneNumber || event.organizer.phone || "—"; + } + + return event.phoneNumber || event.phone || "—"; +} + +function renderSocialLinks(event) { + const organizer = event.organizer && typeof event.organizer === "object" + ? event.organizer + : {}; + + const links = + organizer.socialLinks || + organizer.website || + organizer.socialLink || + organizer.link || + event.socialLinks || + event.website || + ""; + + if (!links) { + elements.socialLinks.textContent = "—"; + return; + } + + if (Array.isArray(links)) { + elements.socialLinks.innerHTML = links + .map( + (link) => + `${link}` + ) + .join("
    "); + return; + } + + elements.socialLinks.innerHTML = `${links}`; +} + +function getLocation(event) { + if (typeof event.location === "string" && event.location.trim()) { + return event.location; + } + + if (event.location && typeof event.location === "object") { + const venue = event.location.venue || ""; + const city = event.location.city || ""; + const state = event.location.state || ""; + return [venue, city || state].filter(Boolean).join(", ") || "—"; + } + + return event.city || "—"; +} + +function getSlotsText(event) { + const filled = + event.filledSlots ?? + event.registrationsCount ?? + event.registeredCount ?? + event.attendeesCount ?? + 0; + + const total = + event.volunteerSlots ?? + event.totalSlots ?? + event.capacity ?? + "—"; + + return `${filled} / ${total} Volunteers`; +} + +function renderEvent(event) { + currentEvent = event; + + const status = getEventStatus(event); + + elements.eventTitle.textContent = event.name || event.title || "Untitled Event"; + elements.orgName.textContent = getOrganizerName(event); + elements.email.textContent = getOrganizerEmail(event); + elements.phoneNumber.textContent = getOrganizerPhone(event); + + const datePart = formatDate(event.date); + const start = event.startTime || ""; + const end = event.endTime ? ` - ${event.endTime}` : ""; + elements.dateTime.innerHTML = `${datePart}${start ? `
    ${start}${end}` : ""}`; + + elements.slotsFilled.textContent = getSlotsText(event); + elements.description.textContent = event.description || "No event description available."; + + renderSocialLinks(event); + setStatusBadge(status); + + if (status === "cancelled" || status === "flagged") { + elements.flagBtn.disabled = true; + elements.flagBtn.textContent = status === "cancelled" ? "Cancelled" : "Flagged"; + } else { + elements.flagBtn.disabled = false; + elements.flagBtn.textContent = "Flag Event"; + } +} + +async function loadEventDetails() { + eventId = getEventIdFromURL(); + + if (!eventId) { + elements.eventTitle.textContent = "Event not found"; + elements.description.textContent = "No event ID was provided in the URL."; + return; + } + + try { + const event = await apiRequest(`/events/${eventId}`); + renderEvent(event); + } catch (error) { + elements.eventTitle.textContent = "Error"; + elements.description.textContent = error.message; + elements.statusBadge.className = "status-badge cancelled"; + elements.statusBadge.textContent = "Unavailable"; + } +} + +async function flagEvent() { + if (!currentEvent) return; + + const confirmed = window.confirm("Flag this event? This will cancel it."); + if (!confirmed) return; + + try { + elements.flagBtn.disabled = true; + elements.flagBtn.textContent = "Processing..."; + elements.feedback.textContent = ""; + + try { + await apiRequest(`/events/${eventId}/cancel/admin`, { + method: "PATCH", + body: JSON.stringify({ + reason: "Flagged by admin" + }) + }); + } catch { + await apiRequest(`/events/${eventId}/cancel`, { + method: "PATCH", + body: JSON.stringify({ + reason: "Flagged by admin" + }) + }); + } + + currentEvent.status = "cancelled"; + renderEvent(currentEvent); + + elements.feedback.textContent = "Event flagged successfully."; + elements.feedback.className = "feedback success"; + } catch (error) { + elements.feedback.textContent = error.message; + elements.feedback.className = "feedback error"; + renderEvent(currentEvent); + } +} + +function closeModal() { + window.location.href = "events-oversight.html"; +} + +elements.closeBtn.addEventListener("click", closeModal); +elements.flagBtn.addEventListener("click", flagEvent); + +elements.overlay.addEventListener("click", (event) => { + if (event.target === elements.overlay) { + closeModal(); + } +}); + +document.addEventListener("DOMContentLoaded", loadEventDetails); + + + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { getQueryParam, formatDate, getLocationText } from "../../assets/js/utils.js"; + +// const id = getQueryParam("id"); +// let currentEvent = null; + +// async function load() { +// const payload = await apiRequest("/events"); +// const events = normalizeArray(payload, ["events"]); +// currentEvent = events.find((e) => String(e._id || e.id) === String(id)); +// document.getElementById("eventTitle").textContent = currentEvent?.name || "Event not found"; +// document.getElementById("eventInfo").textContent = `${formatDate(currentEvent?.date, "long")} • ${getLocationText(currentEvent)}`; +// } + +// document.getElementById("cancelBtn").addEventListener("click", async () => { +// try { +// await apiRequest(`/events/${id}/cancel`, { method: "PATCH", body: JSON.stringify({ reason: "Flagged by admin" }) }); +// document.getElementById("feedback").textContent = "Event cancelled successfully."; +// document.getElementById("feedback").className = "success-message"; +// } catch (err) { +// document.getElementById("feedback").textContent = err.message || "Failed to cancel event."; +// document.getElementById("feedback").className = "form-error"; +// } +// }); + +// document.addEventListener("DOMContentLoaded", async () => { await requireAdmin(); await load(); }); diff --git a/Admin/events/events-oversight.css b/Admin/events/events-oversight.css new file mode 100644 index 0000000..c938d26 --- /dev/null +++ b/Admin/events/events-oversight.css @@ -0,0 +1,387 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Poppins", sans-serif; +} + +body { + background: #eef3f7; + color: #172033; +} + +.dashboard { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 15.875rem; + background: #223f6b; + color: #ffffff; + padding: 2rem 1rem; +} + +.sidebar-logo { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + margin-bottom: 3rem; +} + +.sidebar-logo img { + width: 3.2rem; + height: 3.2rem; + object-fit: contain; +} + +.sidebar-logo h2 { + font-size: 1.25rem; + font-weight: 700; + color: #1d7de2; +} + +.sidebar-logo h2 span { + color: #27a657; +} + +.sidebar-menu { + list-style: none; +} + +.sidebar-menu li { + margin-bottom: 0.9rem; +} + +.sidebar-menu li a { + display: flex; + align-items: center; + gap: 0.95rem; + text-decoration: none; + color: #ffffff; + padding: 0.95rem 0.9rem; + border-radius: 0.45rem; + font-size: 0.98rem; + transition: background 0.2s ease; +} + +.sidebar-menu li a i { + width: 1rem; + text-align: center; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover { + background: #1d7de2; +} + +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.topbar { + height: 5.8rem; + background: #ffffff; + border-bottom: 0.0625rem solid #e6e8eb; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1.9rem; +} + +.search-box { + width: 24rem; + height: 3rem; + display: flex; + align-items: center; + gap: 0.8rem; + border: 0.0625rem solid #d9dde3; + border-radius: 0.4rem; + padding: 0 0.95rem; + background: #ffffff; +} + +.search-box i { + color: #6b7280; +} + +.search-box input { + border: none; + outline: none; + width: 100%; + background: transparent; + font-size: 1rem; + color: #374151; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 1.2rem; +} + +.notification { + position: relative; + color: #111827; + font-size: 1.35rem; +} + +.notification-dot { + position: absolute; + top: 0.08rem; + right: -0.12rem; + width: 0.48rem; + height: 0.48rem; + background: #ef2f2f; + border-radius: 50%; +} + +.admin-profile { + display: flex; + align-items: center; + gap: 0.8rem; +} + +.user-avatar { + width: 2.65rem; + height: 2.65rem; + border-radius: 50%; + object-fit: cover; +} + +.admin-text h4 { + font-size: 0.95rem; + font-weight: 600; + color: #172033; + line-height: 1.2; +} + +.admin-text p { + font-size: 0.83rem; + color: #6b7280; + line-height: 1.2; +} + +.admin-chevron { + color: #6b7280; + font-size: 0.78rem; +} + +.page-content { + padding: 3rem 1.9rem; +} + +.page-header { + margin-bottom: 1.25rem; +} + +.page-header h1 { + font-size: 2.6rem; + line-height: 1.1; + font-weight: 700; + margin-bottom: 0.45rem; +} + +.page-header p { + font-size: 1rem; + color: #6b7280; +} + +.filter-buttons { + display: flex; + gap: 0.7rem; + align-items: center; + margin-bottom: 2rem; +} + +.filter-btn { + border: 0.0625rem solid #d9dde3; + background: #ffffff; + color: #1f2937; + padding: 0.8rem 1rem; + border-radius: 0.45rem; + font-size: 0.92rem; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 0.45rem; +} + +.filter-btn.active { + background: #e8f2ff; + border-color: #e8f2ff; + color: #1d7de2; +} + +.table-wrapper { + background: #ffffff; + border: 0.0625rem solid #e3e7eb; + border-radius: 0.45rem; + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #eef1f4; +} + +th, +td { + text-align: left; + padding: 1.35rem 1.2rem; +} + +th { + font-size: 0.66rem; + letter-spacing: 0.04em; + color: #7a8290; + font-weight: 700; +} + +td { + font-size: 0.96rem; + color: #172033; + border-top: 0.0625rem solid #edf0f2; + vertical-align: middle; +} + +.org-cell { + display: flex; + align-items: center; + gap: 0.7rem; +} + +.org-icon { + width: 2.35rem; + height: 2.35rem; + border-radius: 0.3rem; + background: #eef1f4; + display: flex; + align-items: center; + justify-content: center; + color: #697386; + flex-shrink: 0; +} + +.org-info h4 { + font-size: 0.96rem; + font-weight: 600; + color: #172033; + line-height: 1.25; + margin-bottom: 0.15rem; +} + +.org-info p { + font-size: 0.8rem; + color: #6b7280; + line-height: 1.2; +} + +.status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 4.8rem; + padding: 0.5rem 0.9rem; + border-radius: 0.6rem; + font-size: 0.72rem; + font-weight: 700; + color: #ffffff; +} + +.status-badge.published { + background: #16a34a; +} + +.status-badge.cancelled { + background: #dc2626; +} + +.details-btn { + border: none; + background: #eef1f4; + color: #172033; + padding: 0.8rem 1rem; + border-radius: 0.35rem; + font-size: 0.85rem; + font-weight: 500; + cursor: pointer; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.details-btn:hover { + opacity: 0.92; +} + +.details-btn:active { + transform: scale(0.98); +} + + +.empty-state { + min-height: 14rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.9rem; + text-align: center; + padding: 3rem 1rem; +} + +.empty-icon { + font-size: 3rem; + color: #8b8f96; +} + +@media (max-width: 75rem) { + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 60rem; + } +} + +@media (max-width: 62rem) { + .sidebar { + display: none; + } + + .topbar { + padding: 0 1rem; + } + + .search-box { + width: 16rem; + } + + .page-content { + padding: 1.5rem 1rem; + } + + .page-header h1 { + font-size: 2rem; + } + + .admin-text, + .admin-chevron { + display: none; + } +} + + + + + + +/* uses shared */ \ No newline at end of file diff --git a/Admin/events/events-oversight.html b/Admin/events/events-oversight.html new file mode 100644 index 0000000..c260f46 --- /dev/null +++ b/Admin/events/events-oversight.html @@ -0,0 +1,188 @@ + + + + + + AIDLoop - Events Oversight + + + + +
    + + +
    +
    + + +
    +
    + + +
    + +
    + Admin Avatar +
    +

    Loading...

    +

    Admin

    +
    + +
    +
    +
    + +
    + + +
    + + + +
    + +
    + + + + + + + + + + + + + + + + +
    EVENT NAMECONTACT EMAILLOCATIONSTATUSACTIONS
    Loading events...
    + + +
    +
    +
    +
    + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/events/events-oversight.js b/Admin/events/events-oversight.js new file mode 100644 index 0000000..5ca7c47 --- /dev/null +++ b/Admin/events/events-oversight.js @@ -0,0 +1,278 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + searchInput: document.getElementById("searchInput"), + eventsTable: document.getElementById("eventsTable"), + emptyState: document.getElementById("emptyState"), + adminName: document.getElementById("adminName"), + adminRole: document.getElementById("adminRole"), + adminAvatar: document.getElementById("adminAvatar"), + filterButtons: document.querySelectorAll(".filter-btn") +}; + +let allEvents = []; +let currentFilter = "all"; + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeEvents(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.events)) return payload.events; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function getEventStatus(event) { + const status = String(event.status || "").toLowerCase(); + + if (status === "cancelled" || status === "canceled") { + return "cancelled"; + } + + if (status === "published") { + return "published"; + } + + return status || "published"; +} + +function getEventName(event) { + return event.name || event.title || "Untitled Event"; +} + +function getOrganizerName(event) { + if (typeof event.organizer === "object" && event.organizer) { + return ( + event.organizer.fullName || + event.organizer.name || + event.organizer.organizationName || + "Organizer" + ); + } + + return event.organizerName || "Organizer"; +} + +function getOrganizerEmail(event) { + if (typeof event.organizer === "object" && event.organizer) { + return event.organizer.email || "—"; + } + + return event.contactEmail || event.email || "—"; +} + +function getLocation(event) { + if (typeof event.location === "string" && event.location.trim()) { + return event.location; + } + + if (event.location && typeof event.location === "object") { + const venue = event.location.venue || ""; + const city = event.location.city || ""; + const state = event.location.state || ""; + + return [venue, city || state].filter(Boolean).join(", ") || "—"; + } + + return event.city || "—"; +} + +function badgeText(status) { + if (status === "cancelled") return "Cancelled"; + if (status === "published") return "Published"; + return status.charAt(0).toUpperCase() + status.slice(1); +} + +function renderEvents() { + const query = elements.searchInput.value.trim().toLowerCase(); + + const filteredEvents = allEvents.filter((event) => { + const status = event._eventStatus; + + const matchesFilter = + currentFilter === "all" ? true : status === currentFilter; + + const searchableText = ` + ${getEventName(event)} + ${getOrganizerName(event)} + ${getOrganizerEmail(event)} + ${getLocation(event)} + ${status} + `.toLowerCase(); + + return matchesFilter && searchableText.includes(query); + }); + + if (!filteredEvents.length) { + elements.eventsTable.innerHTML = ""; + elements.emptyState.style.display = "flex"; + return; + } + + elements.emptyState.style.display = "none"; + + elements.eventsTable.innerHTML = filteredEvents + .map((event) => { + const id = event._id || event.id || ""; + const status = event._eventStatus; + + return ` + + +
    +
    + +
    +
    +

    ${getEventName(event)}

    +

    ${getOrganizerName(event)}

    +
    +
    + + ${getOrganizerEmail(event)} + ${getLocation(event)} + ${badgeText(status)} + + + `; + }) + .join(""); + + attachDetailHandlers(); +} + +function attachDetailHandlers() { + document.querySelectorAll(".details-btn").forEach((button) => { + button.addEventListener("click", () => { + const eventId = button.dataset.id; + window.location.href = `event-details.html?id=${encodeURIComponent(eventId)}`; + }); + }); +} + +async function loadAdminProfile() { + try { + let profile; + + try { + profile = await apiRequest("/users/me"); + } catch { + profile = await apiRequest("/user/me"); + } + + elements.adminName.textContent = profile.fullName || profile.name || "Admin"; + elements.adminRole.textContent = profile.role + ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1) + : "Admin"; + + if (profile.profileImage) { + elements.adminAvatar.src = profile.profileImage; + } + } catch (error) { + console.error("Failed to load admin profile:", error.message); + window.location.href = "../login/admin-login.html"; + } +} + +async function loadEvents() { + try { + const eventsPayload = await apiRequest("/events"); + allEvents = normalizeEvents(eventsPayload).map((event) => ({ + ...event, + _eventStatus: getEventStatus(event) + })); + + renderEvents(); + } catch (error) { + console.error("Failed to load events:", error.message); + elements.eventsTable.innerHTML = ` + + Failed to load events. + + `; + } +} + +function bindFilters() { + elements.filterButtons.forEach((button) => { + button.addEventListener("click", () => { + elements.filterButtons.forEach((btn) => btn.classList.remove("active")); + button.classList.add("active"); + currentFilter = button.dataset.filter; + renderEvents(); + }); + }); +} + +function bindSearch() { + elements.searchInput.addEventListener("input", renderEvents); +} + +document.addEventListener("DOMContentLoaded", async () => { + bindFilters(); + bindSearch(); + await loadAdminProfile(); + await loadEvents(); +}); + + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { getLocationText } from "../../assets/js/utils.js"; + +// const table = document.getElementById("eventsTable"); +// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireAdmin(); +// try { +// const payload = await apiRequest("/events"); +// const events = normalizeArray(payload, ["events"]); +// table.innerHTML = events.map((event) => ` +// +// ${event.name || "Untitled Event"} +// ${event.organizer?.fullName || event.organizerName || "Organizer"} +// ${getLocationText(event)} +// ${event.status || "published"} +// View Details +// +// `).join("") || `No events found.`; +// } catch { +// table.innerHTML = `Failed to load events.`; +// } +// }); diff --git a/Admin/flags/flag-details.css b/Admin/flags/flag-details.css new file mode 100644 index 0000000..df2bc9c --- /dev/null +++ b/Admin/flags/flag-details.css @@ -0,0 +1,231 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + min-height: 100vh; + background: #edf1f5; +} + +.overlay { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} + +.modal-card { + position: relative; + width: 100%; + max-width: 31rem; + min-height: 38rem; + background: #ffffff; + border-radius: 1.5rem; + box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.12); + padding: 2.8rem 1.7rem 1.9rem; +} + +.close-btn { + position: absolute; + top: 1.4rem; + right: 1.7rem; + border: none; + background: transparent; + color: #111827; + font-size: 2rem; + line-height: 1; + cursor: pointer; +} + +.modal-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + margin-top: 3rem; + margin-bottom: 3rem; + padding: 0 1.05rem; +} + +.modal-header h1 { + font-size: 1.05rem; + font-weight: 500; + color: #172033; + line-height: 1.15; +} + +.severity-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 9rem; + padding: 0.95rem 1rem; + border-radius: 1rem; + font-size: 0.95rem; + font-weight: 700; +} + +.severity-badge.low { + background: #f4d03f; + color: #111111; +} + +.severity-badge.medium { + background: #f39c12; + color: #111111; +} + +.severity-badge.high { + background: #e74c3c; + color: #ffffff; +} + +.details-grid { + display: grid; + grid-template-columns: 1fr 1.18fr; + column-gap: 1.2rem; + row-gap: 2.5rem; + padding: 0 1.05rem; + margin-bottom: 3rem; +} + +.label { + font-size: 0.95rem; + font-weight: 400; + color: #2a3342; +} + +.value { + font-size: 0.95rem; + font-weight: 500; + color: #172033; + line-height: 1.2; + word-break: break-word; +} + +.description-section { + padding: 0 0.15rem; + margin-bottom: 1rem; +} + +.description-section h2 { + font-size: 1.05rem; + font-weight: 400; + color: #2a3342; + margin-bottom: 1rem; +} + +.description-box { + border: 0.08rem solid #7a8494; + border-radius: 1.4rem; + padding: 1.2rem 1.25rem; + min-height: 10rem; + color: #8e8f95; + font-size: 0.95rem; + line-height: 1.32; +} + +.feedback { + min-height: 1.2rem; + margin-bottom: 1rem; + text-align: center; + font-size: 0.9rem; +} + +.feedback.success { + color: #15803d; +} + +.feedback.error { + color: #dc2626; +} + +.action-row { + padding: 0 0.15rem; +} + +.contact-btn { + width: 100%; + border: none; + border-radius: 0.55rem; + padding: 1.15rem 1rem; + background: #16a357; + color: #ffffff; + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.contact-btn:hover { + opacity: 0.92; +} + +.contact-btn:active { + transform: scale(0.98); +} + +@media (max-width: 32rem) { + .modal-card { + min-height: auto; + padding: 2rem 1rem 1.3rem; + } + + .modal-header { + flex-direction: column; + align-items: flex-start; + padding: 0; + margin-top: 2.5rem; + margin-bottom: 2rem; + } + + .severity-badge { + width: 100%; + min-width: auto; + } + + .details-grid { + grid-template-columns: 1fr; + row-gap: 0.75rem; + padding: 0; + margin-bottom: 2.5rem; + } + + .label { + font-weight: 600; + margin-top: 0.75rem; + } + + .description-section { + padding: 0; + } +} + + + + + + + + + +/* .auth-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.auth-card { + width: 100%; + max-width: 32rem; + background: #fff; + padding: 2rem; + border-radius: 1rem; + box-shadow: 0 0.5rem 2rem rgba(0,0,0,0.06); +} */ diff --git a/Admin/flags/flag-details.html b/Admin/flags/flag-details.html new file mode 100644 index 0000000..993f3ca --- /dev/null +++ b/Admin/flags/flag-details.html @@ -0,0 +1,75 @@ + + + + + + Flag Details + + + +
    + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/flags/flag-details.js b/Admin/flags/flag-details.js new file mode 100644 index 0000000..fb5598a --- /dev/null +++ b/Admin/flags/flag-details.js @@ -0,0 +1,248 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + overlay: document.getElementById("overlay"), + closeBtn: document.getElementById("closeBtn"), + orgTitle: document.getElementById("orgTitle"), + orgName: document.getElementById("orgName"), + severityBadge: document.getElementById("severityBadge"), + flagReason: document.getElementById("flagReason"), + lastEventCancelled: document.getElementById("lastEventCancelled"), + description: document.getElementById("description"), + contactBtn: document.getElementById("contactBtn"), + feedback: document.getElementById("feedback") +}; + +let organizerId = null; +let currentFlagRecord = null; + +function getOrganizerIdFromURL() { + const params = new URLSearchParams(window.location.search); + return params.get("id"); +} + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeEvents(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.events)) return payload.events; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function normalizeUsers(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.users)) return payload.users; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function getOrganizerId(event) { + if (typeof event.organizer === "object" && event.organizer) { + return event.organizer._id || event.organizer.id || ""; + } + return event.organizerId || ""; +} + +function getOrganizerName(event) { + if (typeof event.organizer === "object" && event.organizer) { + return ( + event.organizer.fullName || + event.organizer.name || + event.organizer.organizationName || + "Organizer" + ); + } + return event.organizerName || "Organizer"; +} + +function getSeverity(count) { + if (count <= 2) return "low"; + if (count <= 4) return "medium"; + return "high"; +} + +function getSeverityText(severity) { + return severity.charAt(0).toUpperCase() + severity.slice(1); +} + +function buildFlagData(events, users) { + const usersMap = new Map( + users + .filter((user) => String(user.role || "").toLowerCase() === "organizer") + .map((user) => [String(user._id || user.id), user]) + ); + + const cancelledEvents = events.filter((event) => { + const status = String(event.status || "").toLowerCase(); + return status === "cancelled" || status === "canceled"; + }); + + const grouped = new Map(); + + cancelledEvents.forEach((event) => { + const orgId = String(getOrganizerId(event)); + const organizerName = getOrganizerName(event); + + if (!grouped.has(orgId)) { + grouped.set(orgId, { + organizerId: orgId, + organizerName, + organizer: usersMap.get(orgId) || null, + cancellations: 0, + lastEventDate: null, + lastEventName: "", + reason: "Frequent Event Cancellation" + }); + } + + const record = grouped.get(orgId); + record.cancellations += 1; + + const eventDate = event.date || event.updatedAt || event.createdAt || null; + if (!record.lastEventDate || new Date(eventDate) > new Date(record.lastEventDate)) { + record.lastEventDate = eventDate; + record.lastEventName = event.name || event.title || "Untitled Event"; + } + + if (event.cancelReason || event.reason) { + record.reason = event.cancelReason || event.reason; + } + }); + + return Array.from(grouped.values()).map((record) => ({ + ...record, + severity: getSeverity(record.cancellations) + })); +} + +function renderFlagRecord(record) { + currentFlagRecord = record; + + elements.orgTitle.innerHTML = record.organizerName; + elements.orgName.innerHTML = record.organizerName; + elements.flagReason.textContent = record.reason || "Frequent Event Cancellation"; + elements.lastEventCancelled.textContent = record.lastEventName || "—"; + elements.description.textContent = + record.organizer?.description || + record.organizer?.bio || + "No organizer description available."; + + elements.severityBadge.className = `severity-badge ${record.severity}`; + elements.severityBadge.textContent = getSeverityText(record.severity); + + const email = record.organizer?.email || ""; + elements.contactBtn.onclick = () => { + if (!email) { + elements.feedback.textContent = "Organizer email not available."; + elements.feedback.className = "feedback error"; + return; + } + window.location.href = `mailto:${email}`; + }; +} + +async function loadFlagDetails() { + organizerId = getOrganizerIdFromURL(); + + if (!organizerId) { + elements.orgTitle.textContent = "Flag not found"; + elements.description.textContent = "No organizer ID was provided in the URL."; + return; + } + + try { + const [eventsPayload, usersPayload] = await Promise.all([ + apiRequest("/events"), + apiRequest("/user").catch(() => apiRequest("/users")) + ]); + + const events = normalizeEvents(eventsPayload); + const users = normalizeUsers(usersPayload); + const flagRecords = buildFlagData(events, users); + + const record = flagRecords.find( + (item) => String(item.organizerId) === String(organizerId) + ); + + if (!record) { + throw new Error("Flag details not found."); + } + + renderFlagRecord(record); + } catch (error) { + elements.orgTitle.textContent = "Error"; + elements.description.textContent = error.message; + elements.severityBadge.className = "severity-badge high"; + elements.severityBadge.textContent = "Unavailable"; + } +} + +function closeModal() { + window.location.href = "Flags.html"; +} + +elements.closeBtn.addEventListener("click", closeModal); + +elements.overlay.addEventListener("click", (event) => { + if (event.target === elements.overlay) { + closeModal(); + } +}); + +document.addEventListener("DOMContentLoaded", loadFlagDetails); + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { getQueryParam, formatDate } from "../../assets/js/utils.js"; + +// const id = getQueryParam("id"); + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireAdmin(); +// try { +// const [eventsPayload, usersPayload] = await Promise.all([apiRequest("/events"), apiRequest("/user")]); +// const events = normalizeArray(eventsPayload, ["events"]); +// const users = normalizeArray(usersPayload, ["users"]); +// const org = users.find((u) => String(u._id || u.id) === String(id)); +// const cancelled = events.filter((e) => String(e.organizer?._id || e.organizerId || "") === String(id) && ["cancelled", "canceled"].includes(String(e.status || "").toLowerCase())); +// document.getElementById("orgTitle").textContent = org?.fullName || org?.name || org?.organizationName || "Organization"; +// document.getElementById("flagReason").textContent = cancelled[0]?.cancelReason || cancelled[0]?.reason || "Frequent cancellations"; +// document.getElementById("lastEvent").textContent = cancelled[0] ? `${cancelled[0].name} • ${formatDate(cancelled[0].date, "long")}` : "—"; +// } catch { +// document.getElementById("orgTitle").textContent = "Failed to load flag details."; +// } +// }); diff --git a/Admin/flags/flags.css b/Admin/flags/flags.css new file mode 100644 index 0000000..46bc58d --- /dev/null +++ b/Admin/flags/flags.css @@ -0,0 +1,435 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + background: #eef3f7; + color: #172033; +} + +.dashboard { + display: flex; + min-height: 100vh; +} + +/* Sidebar */ +.sidebar { + width: 14.75rem; + background: #223f6b; + color: #ffffff; + padding: 2rem 0.875rem; +} + +.sidebar-logo { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + margin-bottom: 2.8rem; +} + +.sidebar-logo img { + width: 3.1rem; + height: 3.1rem; + object-fit: contain; +} + +.sidebar-logo h2 { + font-size: 1.25rem; + font-weight: 700; + color: #1c7ae3; +} + +.sidebar-logo h2 span { + color: #26a653; +} + +.sidebar-menu { + list-style: none; +} + +.sidebar-menu li { + margin-bottom: 0.85rem; +} + +.sidebar-menu li a { + display: flex; + align-items: center; + gap: 0.95rem; + text-decoration: none; + color: #ffffff; + padding: 0.95rem 0.9rem; + border-radius: 0.5rem; + font-size: 0.98rem; + transition: background 0.2s ease; +} + +.sidebar-menu li a i { + width: 1rem; + text-align: center; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover { + background: #1d7de2; +} + +/* Main */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + +/* Topbar */ +.topbar { + height: 6rem; + background: #ffffff; + border-bottom: 0.0625rem solid #e6e8eb; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1.9rem; +} + +.search-box { + width: 22.875rem; + height: 3rem; + display: flex; + align-items: center; + gap: 0.8rem; + border: 0.0625rem solid #d9dde3; + border-radius: 0.4rem; + padding: 0 0.95rem; + background: #ffffff; +} + +.search-box i { + color: #6b7280; +} + +.search-box input { + border: none; + outline: none; + width: 100%; + background: transparent; + font-size: 1rem; + color: #374151; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 1.2rem; +} + +.notification { + position: relative; + color: #111827; + font-size: 1.35rem; +} + +.notification-dot { + position: absolute; + top: 0.1rem; + right: -0.12rem; + width: 0.48rem; + height: 0.48rem; + background: #ef2f2f; + border-radius: 50%; +} + +.admin-profile { + display: flex; + align-items: center; + gap: 0.8rem; +} + +.user-avatar { + width: 2.65rem; + height: 2.65rem; + border-radius: 50%; + object-fit: cover; +} + +.admin-text h4 { + font-size: 0.95rem; + font-weight: 600; + color: #172033; + line-height: 1.2; +} + +.admin-text p { + font-size: 0.83rem; + color: #6b7280; + line-height: 1.2; +} + +.admin-chevron { + color: #6b7280; + font-size: 0.78rem; +} + +/* Page content */ +.page-content { + padding: 2rem 1.9rem; +} + +.page-header { + margin-bottom: 1.2rem; +} + +.page-title-group h1 { + font-size: 2.5rem; + line-height: 1.1; + font-weight: 700; + margin-bottom: 0.45rem; +} + +.page-title-group p { + font-size: 1rem; + color: #6b7280; + margin-bottom: 1.35rem; +} + +/* Filters */ +.filter-buttons { + display: flex; + gap: 0.7rem; + align-items: center; +} + +.filter-btn { + border: 0.0625rem solid #d9dde3; + background: #ffffff; + color: #1f2937; + padding: 0.8rem 1rem; + border-radius: 0.45rem; + font-size: 0.92rem; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 0.45rem; +} + +.filter-btn.active { + background: #e8f2ff; + border-color: #e8f2ff; + color: #1d7de2; +} + +/* Table */ +.table-wrapper { + background: #ffffff; + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #eef1f4; +} + +th, +td { + text-align: left; + padding: 1.35rem 1.15rem; +} + +th { + font-size: 0.66rem; + letter-spacing: 0.04em; + color: #7a8290; + font-weight: 700; +} + +td { + font-size: 0.96rem; + color: #172033; + border-top: 0.0625rem solid #edf0f2; + vertical-align: middle; +} + +/* Organization cell */ +.org-cell { + display: flex; + align-items: center; + gap: 0.7rem; +} + +.org-icon { + width: 2.25rem; + height: 2.25rem; + border-radius: 0.35rem; + background: #eef1f4; + display: flex; + align-items: center; + justify-content: center; + color: #697386; + flex-shrink: 0; +} + +.org-info h4 { + font-size: 0.96rem; + font-weight: 600; + color: #172033; + line-height: 1.25; + margin-bottom: 0.15rem; +} + +.org-info p { + font-size: 0.8rem; + color: #6b7280; + line-height: 1.2; +} + +/* Cancellation cell */ +.cancellation-cell { + display: flex; + align-items: center; + gap: 1rem; +} + +.cancel-count { + font-weight: 600; + color: #172033; +} + +.severity-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 3.75rem; + padding: 0.45rem 0.75rem; + border-radius: 0.55rem; + font-size: 0.72rem; + font-weight: 700; + color: #172033; +} + +.severity-badge.low { + background: #f1cf4e; +} + +.severity-badge.medium { + background: #f6ad3f; +} + +.severity-badge.high { + background: #eb2d2d; + color: #ffffff; +} + + + +.flag-reason { + font-size: 0.86rem; + font-weight: 600; + color: #172033; + line-height: 1.2; +} + +.action-links a { + color: #1d7de2; + text-decoration: none; + font-size: 0.83rem; + font-weight: 600; +} + +.action-links a:hover { + text-decoration: underline; +} + +.empty-message { + display: none; + padding: 2rem; + text-align: center; + color: #6b7280; + font-size: 0.95rem; +} + +.status-badge.verified { + background: #16a34a; + color: #fff; +} + +.status-badge.rejected { + background: #dc2626; + color: #fff; +} + + +.empty-state { + min-height: 14rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.9rem; + text-align: center; + padding: 3rem 1rem; +} + +.empty-icon { + font-size: 3rem; + color: #8b8f96; +} + +@media (max-width: 75rem) { + .filter-buttons { + flex-wrap: wrap; + } + + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 60rem; + } +} + +@media (max-width: 62rem) { + .sidebar { + display: none; + } + + .topbar { + padding: 0 1rem; + } + + .search-box { + width: 16rem; + } + + .page-content { + padding: 1.5rem 1rem; + } + + .page-title-group h1 { + font-size: 2rem; + } + + .admin-text, + .admin-chevron { + display: none; + } +} + + + + + + + + + + +/* uses shared */ \ No newline at end of file diff --git a/Admin/flags/flags.html b/Admin/flags/flags.html new file mode 100644 index 0000000..e2a8b45 --- /dev/null +++ b/Admin/flags/flags.html @@ -0,0 +1,189 @@ + + + + + + AIDLoop - Flags + + + + +
    + !-- Sidebar -- + + + !-- Main Content -- +
    +
    + + +
    +
    + + +
    + +
    + Admin Avatar +
    +

    Loading...

    +

    Admin

    +
    + +
    +
    +
    + +
    + + +
    + + + + + + + + + + + + + + + + + +
    ORGANIZATION NAMENUMBER OF CANCELLATIONSSEVERITYLAST EVENT DATEFLAG REASONACTIONS
    Loading flags...
    + + +
    +
    +
    +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/flags/flags.js b/Admin/flags/flags.js new file mode 100644 index 0000000..b345ea3 --- /dev/null +++ b/Admin/flags/flags.js @@ -0,0 +1,364 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + searchInput: document.getElementById("searchInput"), + flagsTable: document.getElementById("flagsTable"), + emptyState: document.getElementById("emptyState"), + adminName: document.getElementById("adminName"), + adminRole: document.getElementById("adminRole"), + adminAvatar: document.getElementById("adminAvatar"), + filterButtons: document.querySelectorAll(".filter-btn") +}; + +let flaggedOrganizations = []; +let currentFilter = "all"; + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeEvents(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.events)) return payload.events; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function normalizeUsers(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.users)) return payload.users; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function getUserStatus(user) { + const status = String(user.status || "").toLowerCase(); + const approvalStatus = String(user.approvalStatus || "").toLowerCase(); + const isVerified = Boolean(user.isVerified); + + if (status === "rejected" || approvalStatus === "rejected") return "rejected"; + if ( + status === "verified" || + status === "approved" || + approvalStatus === "verified" || + approvalStatus === "approved" || + isVerified + ) { + return "verified"; + } + + return "awaiting"; +} + +function getSeverity(count) { + if (count <= 2) return "low"; + if (count <= 4) return "medium"; + return "high"; +} + +function getSeverityText(severity) { + return severity.charAt(0).toUpperCase() + severity.slice(1); +} + +function formatDate(dateValue) { + if (!dateValue) return "—"; + const date = new Date(dateValue); + if (Number.isNaN(date.getTime())) return dateValue; + + return date.toLocaleDateString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric" + }); +} + +function getOrganizerId(event) { + if (typeof event.organizer === "object" && event.organizer) { + return event.organizer._id || event.organizer.id || ""; + } + return event.organizerId || ""; +} + +function getOrganizerName(event) { + if (typeof event.organizer === "object" && event.organizer) { + return ( + event.organizer.fullName || + event.organizer.name || + event.organizer.organizationName || + "Organizer" + ); + } + return event.organizerName || "Organizer"; +} + +function getOrganizerSubtitle(user) { + return user?.tagline || user?.organizationType || user?.bio || "Registered organization"; +} + +function buildFlagData(events, users) { + const usersMap = new Map( + users + .filter((user) => String(user.role || "").toLowerCase() === "organizer") + .map((user) => [String(user._id || user.id), user]) + ); + + const cancelledEvents = events.filter((event) => { + const status = String(event.status || "").toLowerCase(); + return status === "cancelled" || status === "canceled"; + }); + + const grouped = new Map(); + + cancelledEvents.forEach((event) => { + const organizerId = String(getOrganizerId(event)); + const organizerName = getOrganizerName(event); + + if (!grouped.has(organizerId)) { + grouped.set(organizerId, { + organizerId, + organizerName, + organizer: usersMap.get(organizerId) || null, + cancellations: 0, + lastEventDate: null, + lastEventName: "", + reason: "Frequent Cancellations" + }); + } + + const record = grouped.get(organizerId); + record.cancellations += 1; + + const eventDate = event.date || event.updatedAt || event.createdAt || null; + if (!record.lastEventDate || new Date(eventDate) > new Date(record.lastEventDate)) { + record.lastEventDate = eventDate; + record.lastEventName = event.name || event.title || "Untitled Event"; + } + + if (event.cancelReason || event.reason) { + record.reason = event.cancelReason || event.reason; + } + }); + + return Array.from(grouped.values()).map((item) => ({ + ...item, + severity: getSeverity(item.cancellations), + userStatus: item.organizer ? getUserStatus(item.organizer) : "awaiting" + })); +} + +function renderFlags() { + const query = elements.searchInput.value.trim().toLowerCase(); + + const filtered = flaggedOrganizations.filter((item) => { + const matchesFilter = + currentFilter === "all" ? true : item.userStatus === currentFilter; + + const searchableText = ` + ${item.organizerName} + ${item.reason} + ${item.lastEventName} + ${item.userStatus} + ${item.severity} + `.toLowerCase(); + + return matchesFilter && searchableText.includes(query); + }); + + if (!filtered.length) { + elements.flagsTable.innerHTML = ""; + elements.emptyState.style.display = "flex"; + return; + } + + elements.emptyState.style.display = "none"; + + elements.flagsTable.innerHTML = filtered + .map((item) => ` + + +
    +
    + +
    +
    +

    ${item.organizerName}

    +

    ${getOrganizerSubtitle(item.organizer)}

    +
    +
    + + ${item.cancellations} + + + ${getSeverityText(item.severity)} + + + ${formatDate(item.lastEventDate)} + ${item.reason} + + Review | + Contact + + + `) + .join(""); + + attachReviewHandlers(); +} + +function attachReviewHandlers() { + document.querySelectorAll(".review-link").forEach((link) => { + link.addEventListener("click", (event) => { + event.preventDefault(); + const organizerId = link.dataset.id; + window.location.href = `flag-details.html?id=${encodeURIComponent(organizerId)}`; + }); + }); +} + +async function loadAdminProfile() { + try { + let profile; + + try { + profile = await apiRequest("/users/me"); + } catch { + profile = await apiRequest("/user/me"); + } + + elements.adminName.textContent = profile.fullName || profile.name || "Admin"; + elements.adminRole.textContent = profile.role + ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1) + : "Admin"; + + if (profile.profileImage) { + elements.adminAvatar.src = profile.profileImage; + } + } catch (error) { + console.error("Failed to load admin profile:", error.message); + window.location.href = "../login/admin-login.html"; + } +} + +async function loadFlags() { + try { + const [eventsPayload, usersPayload] = await Promise.all([ + apiRequest("/events"), + apiRequest("/user").catch(() => apiRequest("/users")) + ]); + + const events = normalizeEvents(eventsPayload); + const users = normalizeUsers(usersPayload); + + flaggedOrganizations = buildFlagData(events, users); + renderFlags(); + } catch (error) { + console.error("Failed to load flags:", error.message); + elements.flagsTable.innerHTML = ` + + Failed to load flags. + + `; + } +} + +function bindFilters() { + elements.filterButtons.forEach((button) => { + button.addEventListener("click", () => { + elements.filterButtons.forEach((btn) => btn.classList.remove("active")); + button.classList.add("active"); + currentFilter = button.dataset.filter; + renderFlags(); + }); + }); +} + +function bindSearch() { + elements.searchInput.addEventListener("input", renderFlags); +} + +document.addEventListener("DOMContentLoaded", async () => { + bindFilters(); + bindSearch(); + await loadAdminProfile(); + await loadFlags(); +}); + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { formatDate } from "../../assets/js/utils.js"; + +// const table = document.getElementById("flagsTable"); +// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); + +// function severity(count) { +// if (count <= 2) return "Low"; +// if (count <= 4) return "Medium"; +// return "High"; +// } + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireAdmin(); +// try { +// const [eventsPayload, usersPayload] = await Promise.all([apiRequest("/events"), apiRequest("/user")]); +// const events = normalizeArray(eventsPayload, ["events"]); +// const users = normalizeArray(usersPayload, ["users"]); +// const organizers = users.filter((u) => String(u.role || "").toLowerCase() === "organizer"); + +// const flagged = organizers.map((org) => { +// const orgEvents = events.filter((e) => String(e.organizer?._id || e.organizerId || "") === String(org._id || org.id)); +// const cancelled = orgEvents.filter((e) => ["cancelled", "canceled"].includes(String(e.status || "").toLowerCase())); +// return { +// org, +// cancellations: cancelled.length, +// lastDate: cancelled[0]?.date || null, +// reason: cancelled[0]?.cancelReason || cancelled[0]?.reason || (cancelled.length ? "Frequent cancellations" : "—") +// }; +// }).filter((item) => item.cancellations > 0); + +// table.innerHTML = flagged.map((item) => ` +// +// ${item.org.fullName || item.org.name || item.org.organizationName || "Organization"} +// ${item.cancellations} +// ${severity(item.cancellations)} +// ${formatDate(item.lastDate, "long")} +// ${item.reason} +// Review +// +// `).join("") || `No flags found.`; +// } catch { +// table.innerHTML = `Failed to load flags.`; +// } +// }); diff --git a/Admin/login/admin-login.css b/Admin/login/admin-login.css new file mode 100644 index 0000000..ad0a6df --- /dev/null +++ b/Admin/login/admin-login.css @@ -0,0 +1,247 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + background: #f3f5f7; + color: #111827; +} + +.login-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 0.75rem; +} + +.login-card { + width: 100%; + max-width: 42rem; + background: #ffffff; + padding: 2rem 4.5rem 5rem; +} + +.logo-wrap { + text-align: center; + margin-bottom: 3rem; +} + +.logo { + width: 8rem; + height: auto; + display: inline-block; + margin-bottom: 0.5rem; +} + +.tagline { + font-size: 0.9rem; + color: #111827; +} + +h1 { + text-align: center; + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 3.8rem; +} + +.form-group { + margin-bottom: 1.6rem; +} + +label { + display: block; + font-size: 0.9rem; + font-weight: 500; + margin-bottom: 0.7rem; + color: #111827; +} + +input[type="email"], +input[type="password"], +input[type="text"] { + width: 100%; + height: 3.3rem; + border: 0.0625rem solid #9aa8bc; + border-radius: 0.75rem; + padding: 0 1rem; + font-size: 0.95rem; + color: #111827; + outline: none; + background: #ffffff; +} + +input::placeholder { + color: #c3c6cc; +} + +input:focus { + border-color: #2a7de1; +} + +.password-wrap { + position: relative; +} + +.password-wrap input { + padding-right: 3rem; +} + +.toggle-password { + position: absolute; + top: 50%; + right: 1rem; + transform: translateY(-50%); + border: none; + background: transparent; + color: #c3c6cc; + cursor: pointer; + font-size: 1rem; +} + +.remember-wrap { + display: inline-flex; + align-items: center; + gap: 0.55rem; + cursor: pointer; + margin-top: 0.2rem; + margin-bottom: 5.5rem; + user-select: none; +} + +.remember-wrap input { + display: none; +} + +.custom-check { + width: 1.45rem; + height: 1.45rem; + border-radius: 0.4rem; + background: #1d7de2; + display: inline-flex; + align-items: center; + justify-content: center; + color: #ffffff; + font-size: 0.8rem; + flex-shrink: 0; +} + +.remember-wrap input:not(:checked) + .custom-check { + background: #ffffff; + border: 0.0625rem solid #cfd4db; + color: transparent; +} + +.remember-text { + font-size: 0.9rem; + color: #111827; +} + +.button-group { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.btn { + width: 100%; + height: 3.4rem; + border-radius: 0.85rem; + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.btn:hover { + opacity: 0.94; +} + +.btn:active { + transform: scale(0.99); +} + +.btn-primary { + border: none; + background: #223f6b; + color: #ffffff; +} + +.btn-secondary { + border: 0.125rem solid #1d7de2; + background: #ffffff; + color: #223f6b; +} + +.error-message { + display: block; + min-height: 1rem; + margin-top: 0.35rem; + font-size: 0.78rem; + color: #dc2626; +} + +.form-error, +.success-message { + display: block; + margin-bottom: 1rem; + font-size: 0.85rem; + text-align: center; +} + +.form-error { + color: #dc2626; +} + +.success-message { + color: #16a34a; +} + +.btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +@media (max-width: 40rem) { + .login-card { + padding: 1.5rem 1.25rem 2rem; + } + + .remember-wrap { + margin-bottom: 2.5rem; + } +} + + + + + + + + + +/* .auth-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.auth-card { + width: 100%; + max-width: 32rem; + background: #fff; + border-radius: 1rem; + padding: 2rem; + box-shadow: 0 0.5rem 2rem rgba(0,0,0,0.06); +} + +.auth-card h1 { + margin-bottom: 1.2rem; + color: #223f6b; +} */ diff --git a/Admin/login/admin-login.html b/Admin/login/admin-login.html new file mode 100644 index 0000000..87e8153 --- /dev/null +++ b/Admin/login/admin-login.html @@ -0,0 +1,100 @@ + + + + + + AIDLoop Admin Login + + + + +
    + +
    + + + + + + + + + + + + + diff --git a/Admin/login/admin-login.js b/Admin/login/admin-login.js new file mode 100644 index 0000000..29e4b38 --- /dev/null +++ b/Admin/login/admin-login.js @@ -0,0 +1,189 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + loginForm: document.getElementById("loginForm"), + email: document.getElementById("email"), + password: document.getElementById("password"), + rememberMe: document.getElementById("rememberMe"), + togglePassword: document.getElementById("togglePassword"), + forgotPasswordBtn: document.getElementById("forgotPasswordBtn"), + loginBtn: document.getElementById("loginBtn"), + emailError: document.getElementById("emailError"), + passwordError: document.getElementById("passwordError"), + formError: document.getElementById("formError"), + formSuccess: document.getElementById("formSuccess") +}; + +function clearMessages() { + elements.emailError.textContent = ""; + elements.passwordError.textContent = ""; + elements.formError.textContent = ""; + elements.formSuccess.textContent = ""; +} + +function validateEmail(email) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); +} + +function setLoading(isLoading) { + elements.loginBtn.disabled = isLoading; + elements.loginBtn.textContent = isLoading ? "Logging in..." : "Log in"; +} + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + method: options.method || "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + body: options.body || null + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function loadRememberedEmail() { + const savedEmail = localStorage.getItem("aidloop_admin_email"); + if (savedEmail) { + elements.email.value = savedEmail; + elements.rememberMe.checked = true; + } +} + +function saveRememberedEmail() { + if (elements.rememberMe.checked) { + localStorage.setItem("aidloop_admin_email", elements.email.value.trim()); + } else { + localStorage.removeItem("aidloop_admin_email"); + } +} + +async function checkExistingSession() { + try { + const status = await apiRequest("/auth/status"); + if (status) { + window.location.href = "../dashboard/admin-dashboard.html"; + } + } catch { + // stay on page + } +} + +elements.togglePassword.addEventListener("click", () => { + const isPassword = elements.password.type === "password"; + elements.password.type = isPassword ? "text" : "password"; + elements.togglePassword.innerHTML = isPassword + ? '' + : ''; +}); + +elements.forgotPasswordBtn.addEventListener("click", () => { + alert("Forgot password flow is not connected yet."); +}); + +elements.loginForm.addEventListener("submit", async (event) => { + event.preventDefault(); + clearMessages(); + + const email = elements.email.value.trim(); + const password = elements.password.value.trim(); + + let isValid = true; + + if (!email) { + elements.emailError.textContent = "Email address is required."; + isValid = false; + } else if (!validateEmail(email)) { + elements.emailError.textContent = "Enter a valid email address."; + isValid = false; + } + + if (!password) { + elements.passwordError.textContent = "Password is required."; + isValid = false; + } else if (password.length < 6) { + elements.passwordError.textContent = "Password must be at least 6 characters."; + isValid = false; + } + + if (!isValid) return; + + try { + setLoading(true); + + const result = await apiRequest("/auth/login", { + method: "POST", + body: JSON.stringify({ email, password }) + }); + + saveRememberedEmail(); + elements.formSuccess.textContent = result.message || "Login successful."; + + setTimeout(() => { + window.location.href = "../dashboard/admin-dashboard.html"; + }, 700); + } catch (error) { + elements.formError.textContent = error.message || "Login failed."; + } finally { + setLoading(false); + } +}); + +document.addEventListener("DOMContentLoaded", () => { + loadRememberedEmail(); + checkExistingSession(); +}); + + + + + + + + + + + +// import { apiRequest } from "../../assets/js/api.js"; +// import { ROUTES } from "../../assets/js/config.js"; + +// const form = document.getElementById("loginForm"); +// const email = document.getElementById("email"); +// const password = document.getElementById("password"); +// const formError = document.getElementById("formError"); +// const formSuccess = document.getElementById("formSuccess"); + +// form.addEventListener("submit", async (e) => { +// e.preventDefault(); +// formError.textContent = ""; +// formSuccess.textContent = ""; +// try { +// const response = await apiRequest("/auth/login", { +// method: "POST", +// body: JSON.stringify({ email: email.value.trim(), password: password.value.trim() }) +// }); +// if (String(response?.user?.role || "admin").toLowerCase() !== "admin") { +// throw new Error("Not an admin account"); +// } +// localStorage.setItem("aidloop_admin_email", email.value.trim()); +// formSuccess.textContent = response.message || "Login successful"; +// setTimeout(() => { window.location.href = ROUTES.adminDashboard; }, 800); +// } catch (err) { +// formError.textContent = err.message || "Login failed"; +// } +// }); diff --git a/Admin/organizations/organization-details.css b/Admin/organizations/organization-details.css new file mode 100644 index 0000000..a679db5 --- /dev/null +++ b/Admin/organizations/organization-details.css @@ -0,0 +1,190 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + min-height: 100vh; + background: #edf1f5; +} + +.overlay { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} + +.modal-card { + position: relative; + width: 100%; + max-width: 31rem; + min-height: 50rem; + background: #ffffff; + border-radius: 1.5rem; + box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.12); + padding: 2.8rem 1.7rem 2rem; +} + +.close-btn { + position: absolute; + top: 1.35rem; + right: 1.5rem; + border: none; + background: transparent; + color: #111827; + font-size: 2rem; + line-height: 1; + cursor: pointer; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 1rem; + margin-top: 3.3rem; + margin-bottom: 4rem; + padding: 0 0.35rem; +} + +.modal-header h1 { + font-size: 1.05rem; + font-weight: 500; + color: #172033; + line-height: 1.2; +} + +.status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 6.6rem; + padding: 0.8rem 1rem; + border-radius: 1rem; + font-size: 0.95rem; + font-weight: 700; + color: #ffffff; +} + +.status-badge.verified { + background: #16a34a; +} + +.status-badge.rejected { + background: #ef2f2f; +} + +.status-badge.awaiting { + background: #f59e0b; +} + +.details-grid { + display: grid; + grid-template-columns: 1fr 1.2fr; + column-gap: 1.35rem; + row-gap: 1.9rem; + padding: 0 1.5rem; + margin-bottom: 4rem; +} + +.label { + font-size: 0.95rem; + font-weight: 400; + color: #2a3342; +} + +.value { + font-size: 0.95rem; + font-weight: 500; + color: #172033; + line-height: 1.3; + word-break: break-word; +} + +.value a { + color: #172033; + text-decoration: underline; +} + +.description-section { + padding: 0 0.15rem; +} + +.description-section h2 { + font-size: 1.05rem; + font-weight: 400; + color: #172033; + margin-bottom: 1rem; +} + +.description-box { + border: 0.08rem solid #2f4f7e; + border-radius: 1rem; + padding: 1.4rem 1.2rem; + min-height: 8.8rem; + color: #8b8f96; + font-size: 0.95rem; + line-height: 1.35; +} + +@media (max-width: 32rem) { + .modal-card { + min-height: auto; + padding: 2rem 1rem 1.5rem; + } + + .modal-header { + flex-direction: column; + align-items: flex-start; + padding: 0; + margin-top: 2.5rem; + margin-bottom: 2rem; + } + + .details-grid { + grid-template-columns: 1fr; + row-gap: 0.75rem; + padding: 0; + margin-bottom: 2.5rem; + } + + .label { + font-weight: 600; + margin-top: 0.7rem; + } + + .description-section { + padding: 0; + } +} + + + + + + + + + + + +/* .auth-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.auth-card { + width: 100%; + max-width: 32rem; + background: #fff; + padding: 2rem; + border-radius: 1rem; + box-shadow: 0 0.5rem 2rem rgba(0,0,0,0.06); +} */ diff --git a/Admin/organizations/organization-details.html b/Admin/organizations/organization-details.html new file mode 100644 index 0000000..a0be146 --- /dev/null +++ b/Admin/organizations/organization-details.html @@ -0,0 +1,77 @@ + + + + + + Organizer Details + + + +
    + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/organizations/organization-details.js b/Admin/organizations/organization-details.js new file mode 100644 index 0000000..67970d5 --- /dev/null +++ b/Admin/organizations/organization-details.js @@ -0,0 +1,229 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + overlay: document.getElementById("overlay"), + closeBtn: document.getElementById("closeBtn"), + orgTitle: document.getElementById("orgTitle"), + orgName: document.getElementById("orgName"), + socialLinks: document.getElementById("socialLinks"), + email: document.getElementById("email"), + phoneNumber: document.getElementById("phoneNumber"), + location: document.getElementById("location"), + description: document.getElementById("description"), + statusBadge: document.getElementById("statusBadge") +}; + +let organizerId = null; + +function getOrganizerIdFromURL() { + const params = new URLSearchParams(window.location.search); + return params.get("id"); +} + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeUsers(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.users)) return payload.users; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function getStatus(user) { + const status = String(user.status || "").toLowerCase(); + const approvalStatus = String(user.approvalStatus || "").toLowerCase(); + const isVerified = Boolean(user.isVerified); + + if (status === "rejected" || approvalStatus === "rejected") { + return "rejected"; + } + + if ( + status === "verified" || + status === "approved" || + approvalStatus === "verified" || + approvalStatus === "approved" || + isVerified + ) { + return "verified"; + } + + return "awaiting"; +} + +function getStatusText(status) { + if (status === "verified") return "Verified"; + if (status === "rejected") return "Rejected"; + return "Awaiting"; +} + +function getDisplayName(user) { + return user.fullName || user.name || user.organizationName || "Unnamed Organizer"; +} + +function getLocation(user) { + if (typeof user.location === "string" && user.location.trim()) { + return user.location; + } + + if (user.location && typeof user.location === "object") { + return ( + user.location.city || + user.location.state || + user.location.venue || + "—" + ); + } + + return user.city || user.state || "—"; +} + +function renderSocialLinks(user) { + const links = + user.socialLinks || + user.website || + user.socialLink || + user.link || + ""; + + if (!links) { + elements.socialLinks.textContent = "—"; + return; + } + + if (Array.isArray(links)) { + elements.socialLinks.innerHTML = links + .map((link) => `${link}`) + .join("
    "); + return; + } + + elements.socialLinks.innerHTML = `${links}`; +} + +function renderOrganizer(user) { + const displayName = getDisplayName(user); + const status = getStatus(user); + + elements.orgTitle.textContent = displayName; + elements.orgName.textContent = displayName; + elements.email.textContent = user.email || "—"; + elements.phoneNumber.textContent = + user.phoneNumber || + user.phone || + user.mobile || + "—"; + elements.location.textContent = getLocation(user); + elements.description.textContent = + user.description || + user.bio || + "No description available."; + + renderSocialLinks(user); + + elements.statusBadge.className = `status-badge ${status}`; + elements.statusBadge.textContent = getStatusText(status); +} + +async function loadOrganizerDetails() { + organizerId = getOrganizerIdFromURL(); + + if (!organizerId) { + elements.orgTitle.textContent = "Organizer not found"; + elements.description.textContent = "No organizer ID was provided in the URL."; + return; + } + + try { + let usersPayload; + + try { + usersPayload = await apiRequest("/user"); + } catch { + usersPayload = await apiRequest("/users"); + } + + const users = normalizeUsers(usersPayload); + const organizer = users.find( + (user) => String(user._id || user.id) === String(organizerId) + ); + + if (!organizer) { + throw new Error("Organizer not found."); + } + + renderOrganizer(organizer); + } catch (error) { + elements.orgTitle.textContent = "Error"; + elements.description.textContent = error.message; + elements.statusBadge.className = "status-badge rejected"; + elements.statusBadge.textContent = "Unavailable"; + } +} + +function closeModal() { + window.location.href = "organization-directory.html"; +} + +elements.closeBtn.addEventListener("click", closeModal); + +elements.overlay.addEventListener("click", (event) => { + if (event.target === elements.overlay) { + closeModal(); + } +}); + +document.addEventListener("DOMContentLoaded", loadOrganizerDetails); + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { getQueryParam, getLocationText } from "../../assets/js/utils.js"; + +// const id = getQueryParam("id"); + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireAdmin(); +// try { +// const payload = await apiRequest("/user"); +// const users = normalizeArray(payload, ["users"]); +// const org = users.find((u) => String(u._id || u.id) === String(id)); +// document.getElementById("orgName").textContent = org?.fullName || org?.name || org?.organizationName || "Organization"; +// document.getElementById("orgEmail").textContent = org?.email || "—"; +// document.getElementById("orgLocation").textContent = getLocationText(org); +// document.getElementById("orgDescription").textContent = org?.description || org?.bio || "No description available."; +// } catch { +// document.getElementById("orgName").textContent = "Failed to load organization."; +// } +// }); diff --git a/Admin/organizations/organization-directory.css b/Admin/organizations/organization-directory.css new file mode 100644 index 0000000..24f8616 --- /dev/null +++ b/Admin/organizations/organization-directory.css @@ -0,0 +1,401 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + background: #eef3f7; + color: #172033; +} + +.dashboard { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 15rem; + background: #223f6b; + color: #ffffff; + padding: 2rem 0.9rem; +} + +.sidebar-logo { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + margin-bottom: 2.8rem; +} + +.sidebar-logo img { + width: 3.1rem; + height: 3.1rem; + object-fit: contain; +} + +.sidebar-logo h2 { + font-size: 1.25rem; + font-weight: 700; + color: #1d7de2; +} + +.sidebar-logo h2 span { + color: #27a657; +} + +.sidebar-menu { + list-style: none; +} + +.sidebar-menu li { + margin-bottom: 0.85rem; +} + +.sidebar-menu li a { + display: flex; + align-items: center; + gap: 0.95rem; + text-decoration: none; + color: #ffffff; + padding: 0.95rem 0.9rem; + border-radius: 0.45rem; + font-size: 0.98rem; + transition: background 0.2s ease; +} + +.sidebar-menu li a i { + width: 1rem; + text-align: center; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover { + background: #1d7de2; +} + +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.topbar { + height: 6rem; + background: #ffffff; + border-bottom: 0.0625rem solid #e6e8eb; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1.9rem; +} + +.search-box { + width: 22rem; + height: 3rem; + display: flex; + align-items: center; + gap: 0.8rem; + border: 0.0625rem solid #d9dde3; + border-radius: 0.4rem; + padding: 0 0.95rem; + background: #ffffff; +} + +.search-box i { + color: #6b7280; +} + +.search-box input { + border: none; + outline: none; + width: 100%; + background: transparent; + font-size: 1rem; + color: #374151; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 1.2rem; +} + +.notification { + position: relative; + color: #111827; + font-size: 1.35rem; +} + +.notification-dot { + position: absolute; + top: 0.1rem; + right: -0.12rem; + width: 0.48rem; + height: 0.48rem; + background: #ef2f2f; + border-radius: 50%; +} + +.admin-profile { + display: flex; + align-items: center; + gap: 0.8rem; +} + +.user-avatar { + width: 2.65rem; + height: 2.65rem; + border-radius: 50%; + object-fit: cover; +} + +.admin-text h4 { + font-size: 0.95rem; + font-weight: 600; + color: #172033; + line-height: 1.2; +} + +.admin-text p { + font-size: 0.83rem; + color: #6b7280; + line-height: 1.2; +} + +.admin-chevron { + color: #6b7280; + font-size: 0.78rem; +} + +.page-content { + padding: 2rem 1.9rem; +} + +.page-header { + margin-bottom: 1rem; +} + +.page-header h1 { + font-size: 2.5rem; + line-height: 1.1; + font-weight: 700; + margin-bottom: 0.45rem; +} + +.page-header p { + font-size: 1rem; + color: #6b7280; +} + +.filter-buttons { + display: flex; + gap: 0.7rem; + align-items: center; + margin-bottom: 2rem; +} + +.filter-btn { + border: 0.0625rem solid #d9dde3; + background: #ffffff; + color: #1f2937; + padding: 0.8rem 1rem; + border-radius: 0.45rem; + font-size: 0.92rem; + cursor: pointer; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 0.45rem; +} + +.filter-btn.active { + background: #e8f2ff; + border-color: #e8f2ff; + color: #1d7de2; +} + +.table-wrapper { + background: #ffffff; + border: 0.0625rem solid #e3e7eb; + border-radius: 0.45rem; + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #eef1f4; +} + +th, +td { + text-align: left; + padding: 1.3rem 1.1rem; +} + +th { + font-size: 0.66rem; + letter-spacing: 0.04em; + color: #7a8290; + font-weight: 700; +} + +td { + font-size: 0.95rem; + color: #172033; + border-top: 0.0625rem solid #edf0f2; + vertical-align: middle; +} + +.org-cell { + display: flex; + align-items: center; + gap: 0.7rem; +} + +.org-icon { + width: 2.2rem; + height: 2.2rem; + border-radius: 0.3rem; + background: #eef1f4; + display: flex; + align-items: center; + justify-content: center; + color: #697386; + flex-shrink: 0; +} + +.org-info h4 { + font-size: 0.95rem; + font-weight: 600; + color: #172033; + line-height: 1.25; + margin-bottom: 0.15rem; +} + +.org-info p { + font-size: 0.8rem; + color: #6b7280; + line-height: 1.2; +} + +.status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 4.8rem; + padding: 0.45rem 0.8rem; + border-radius: 0.6rem; + font-size: 0.72rem; + font-weight: 700; + color: #ffffff; +} + +.status-badge.verified { + background: #16a34a; +} + +.status-badge.rejected { + background: #ef2f2f; +} + +.details-btn { + border: none; + background: #eef1f4; + color: #172033; + padding: 0.75rem 1rem; + border-radius: 0.35rem; + font-size: 0.82rem; + font-weight: 600; + cursor: pointer; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.details-btn:hover { + opacity: 0.92; +} + +.details-btn:active { + transform: scale(0.98); +} + + +.empty-state { + min-height: 14rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.9rem; + text-align: center; + padding: 3rem 1rem; +} + +.empty-icon { + position: relative; + width: 4rem; + height: 4rem; + display: flex; + align-items: center; + justify-content: center; + color: #8b8f96; + font-size: 3rem; +} + +.empty-check { + position: absolute; + right: 0.1rem; + bottom: 0.35rem; + font-size: 1.25rem; + background: #ffffff; + border-radius: 50%; +} + +@media (max-width: 75rem) { + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 58rem; + } +} + +@media (max-width: 62rem) { + .sidebar { + display: none; + } + + .topbar { + padding: 0 1rem; + } + + .search-box { + width: 16rem; + } + + .page-content { + padding: 1.5rem 1rem; + } + + .page-header h1 { + font-size: 2rem; + } + + .admin-text, + .admin-chevron { + display: none; + } +} + + + + + +/* uses shared */ \ No newline at end of file diff --git a/Admin/organizations/organization-directory.html b/Admin/organizations/organization-directory.html new file mode 100644 index 0000000..126c124 --- /dev/null +++ b/Admin/organizations/organization-directory.html @@ -0,0 +1,190 @@ + + + + + + AIDLoop - Organizer Directory + + + + +
    + + +
    +
    + + +
    +
    + + +
    + +
    + Admin Avatar +
    +

    Loading...

    +

    Admin

    +
    + +
    +
    +
    + +
    + + +
    + + + +
    + +
    + + + + + + + + + + + + + + + + +
    ORGANIZATION NAMECONTACT EMAILLOCATIONSTATUSACTIONS
    Loading organizations...
    + + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/organizations/organization-directory.js b/Admin/organizations/organization-directory.js new file mode 100644 index 0000000..1db1d24 --- /dev/null +++ b/Admin/organizations/organization-directory.js @@ -0,0 +1,280 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + searchInput: document.getElementById("searchInput"), + directoryTable: document.getElementById("directoryTable"), + emptyState: document.getElementById("emptyState"), + adminName: document.getElementById("adminName"), + adminRole: document.getElementById("adminRole"), + adminAvatar: document.getElementById("adminAvatar"), + filterButtons: document.querySelectorAll(".filter-btn") +}; + +let organizers = []; +let currentFilter = "all"; + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeUsers(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.users)) return payload.users; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function getVerificationStatus(user) { + const status = String(user.status || "").toLowerCase(); + const approvalStatus = String(user.approvalStatus || "").toLowerCase(); + const isVerified = Boolean(user.isVerified); + + if (status === "rejected" || approvalStatus === "rejected") { + return "rejected"; + } + + if ( + status === "verified" || + status === "approved" || + approvalStatus === "verified" || + approvalStatus === "approved" || + isVerified + ) { + return "verified"; + } + + return "awaiting"; +} + +function getDisplayName(user) { + return user.fullName || user.name || user.organizationName || "Unnamed Organizer"; +} + +function getSubtitle(user) { + return user.tagline || user.organizationType || user.bio || "Registered organization"; +} + +function getLocation(user) { + if (typeof user.location === "string" && user.location.trim()) { + return user.location; + } + + if (user.location && typeof user.location === "object") { + return ( + user.location.city || + user.location.state || + user.location.venue || + "—" + ); + } + + return user.city || user.state || "—"; +} + +function badgeText(status) { + if (status === "verified") return "Verified"; + if (status === "rejected") return "Rejected"; + return "Awaiting Verification"; +} + +function renderTable() { + const query = elements.searchInput.value.trim().toLowerCase(); + + const filtered = organizers.filter((organizer) => { + const status = organizer._verificationStatus; + + const matchesFilter = + currentFilter === "all" ? true : status === currentFilter; + + const searchableText = ` + ${getDisplayName(organizer)} + ${organizer.email || ""} + ${getLocation(organizer)} + ${status} + `.toLowerCase(); + + return matchesFilter && searchableText.includes(query); + }); + + if (!filtered.length) { + elements.directoryTable.innerHTML = ""; + elements.emptyState.style.display = "flex"; + return; + } + + elements.emptyState.style.display = "none"; + + elements.directoryTable.innerHTML = filtered + .map((organizer) => { + const id = organizer._id || organizer.id || ""; + const status = organizer._verificationStatus; + + return ` + + +
    +
    + +
    +
    +

    ${getDisplayName(organizer)}

    +

    ${getSubtitle(organizer)}

    +
    +
    + + ${organizer.email || "—"} + ${getLocation(organizer)} + ${badgeText(status)} + + + `; + }) + .join(""); + + attachDetailHandlers(); +} + +function attachDetailHandlers() { + document.querySelectorAll(".details-btn").forEach((button) => { + button.addEventListener("click", () => { + const organizerId = button.dataset.id; + window.location.href = `organization-details.html?id=${encodeURIComponent(organizerId)}`; + }); + }); +} + +async function loadAdminProfile() { + try { + let profile; + + try { + profile = await apiRequest("/users/me"); + } catch { + profile = await apiRequest("/user/me"); + } + + elements.adminName.textContent = profile.fullName || profile.name || "Admin"; + elements.adminRole.textContent = profile.role + ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1) + : "Admin"; + + if (profile.profileImage) { + elements.adminAvatar.src = profile.profileImage; + } + } catch (error) { + console.error("Failed to load admin profile:", error.message); + window.location.href = "../login/admin-login.html"; + } +} + +async function loadOrganizations() { + try { + let usersPayload; + + try { + usersPayload = await apiRequest("/user"); + } catch { + usersPayload = await apiRequest("/users"); + } + + const users = normalizeUsers(usersPayload); + + organizers = users + .filter((user) => String(user.role || "").toLowerCase() === "organizer") + .map((user) => ({ + ...user, + _verificationStatus: getVerificationStatus(user) + })); + + renderTable(); + } catch (error) { + console.error("Failed to load organizations:", error.message); + elements.directoryTable.innerHTML = ` + + Failed to load organizations. + + `; + } +} + +function bindFilters() { + elements.filterButtons.forEach((button) => { + button.addEventListener("click", () => { + elements.filterButtons.forEach((btn) => btn.classList.remove("active")); + button.classList.add("active"); + currentFilter = button.dataset.filter; + renderTable(); + }); + }); +} + +function bindSearch() { + elements.searchInput.addEventListener("input", renderTable); +} + +document.addEventListener("DOMContentLoaded", async () => { + bindFilters(); + bindSearch(); + await loadAdminProfile(); + await loadOrganizations(); +}); + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { getLocationText } from "../../assets/js/utils.js"; + +// const table = document.getElementById("directoryTable"); +// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireAdmin(); +// try { +// const payload = await apiRequest("/user"); +// const users = normalizeArray(payload, ["users"]); +// const organizers = users.filter((u) => String(u.role || "").toLowerCase() === "organizer"); +// table.innerHTML = organizers.map((u) => ` +// +// ${u.fullName || u.name || u.organizationName || "Unnamed Organizer"} +// ${u.email || "—"} +// ${getLocationText(u)} +// ${u.isVerified ? "Verified" : "Pending"} +// View Details +// +// `).join("") || `No organizations found.`; +// } catch { +// table.innerHTML = `Failed to load organizations.`; +// } +// }); diff --git a/Admin/profile/admin-profile.css b/Admin/profile/admin-profile.css new file mode 100644 index 0000000..c57e015 --- /dev/null +++ b/Admin/profile/admin-profile.css @@ -0,0 +1,298 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + background: #eef3f7; + color: #172033; +} + +.dashboard { + display: flex; + min-height: 100vh; +} + +/* Sidebar */ +.sidebar { + width: 16rem; + background: #223f6b; + color: #ffffff; + padding: 2rem 1rem; +} + +.sidebar-logo { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.4rem; + margin-bottom: 3rem; +} + +.sidebar-logo img { + width: 3.25rem; + height: 3.25rem; + object-fit: contain; +} + +.sidebar-logo h2 { + font-size: 1.35rem; + font-weight: 700; + color: #1c7ae3; +} + +.sidebar-logo h2 span { + color: #26a653; +} + +.sidebar-menu { + list-style: none; +} + +.sidebar-menu li { + margin-bottom: 0.8rem; +} + +.sidebar-menu li a { + display: flex; + align-items: center; + gap: 0.9rem; + text-decoration: none; + color: #ffffff; + padding: 0.95rem 1rem; + border-radius: 0.5rem; + font-size: 1rem; + transition: background 0.2s ease; +} + +.sidebar-menu li a i { + width: 1rem; + text-align: center; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover { + background: #1d7de2; +} + +/* Main */ +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + +/* Topbar */ +.topbar { + height: 6rem; + background: #ffffff; + border-bottom: 0.0625rem solid #e6e8eb; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 2rem; +} + +.search-box { + width: 21rem; + height: 3rem; + display: flex; + align-items: center; + gap: 0.8rem; + border: 0.0625rem solid #d9dde3; + border-radius: 0.45rem; + padding: 0 1rem; + background: #ffffff; +} + +.search-box i { + color: #6b7280; +} + +.search-box input { + border: none; + outline: none; + width: 100%; + background: transparent; + font-size: 1rem; + color: #374151; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 1.2rem; +} + +.notification { + position: relative; + color: #111827; + font-size: 1.35rem; +} + +.notification-dot { + position: absolute; + top: 0.08rem; + right: -0.12rem; + width: 0.5rem; + height: 0.5rem; + background: #ef2f2f; + border-radius: 50%; +} + +.user-avatar { + width: 2.8rem; + height: 2.8rem; + object-fit: cover; + border-radius: 50%; +} + +/* Page */ +.page-content { + padding: 2rem; +} + +.page-header { + margin-bottom: 2rem; +} + +.page-header h1 { + font-size: 2.6rem; + font-weight: 700; + margin-bottom: 0.4rem; +} + +.page-header p { + font-size: 1rem; + color: #6b7280; +} + +/* Grid */ +.profile-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.4rem; + align-items: start; +} + +/* Cards */ +.card { + background: #ffffff; + padding: 1.6rem 1.4rem; + border-radius: 0; +} + +.card h2 { + font-size: 2rem; + font-weight: 700; + margin-bottom: 1.8rem; + color: #172033; +} + +/* Form */ +.form { + display: flex; + flex-direction: column; +} + +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + font-size: 0.95rem; + letter-spacing: 0.14em; + color: #111827; + margin-bottom: 0.45rem; +} + +.form-group input { + width: 100%; + height: 3rem; + border: 0.0625rem solid #cfd4db; + border-radius: 0.5rem; + padding: 0 1rem; + font-size: 0.95rem; + color: #172033; + background: #ffffff; +} + +.form-group input[readonly] { + background: #f2f4f7; + border-color: #f2f4f7; + color: #9aa1ab; +} + +.button-row { + display: flex; + justify-content: flex-end; + margin-top: 0.3rem; +} + +.align-right { + justify-content: flex-end; +} + +.primary-btn, +.secondary-btn { + border: none; + border-radius: 0.55rem; + padding: 0.85rem 1.35rem; + font-size: 0.95rem; + font-weight: 600; + color: #ffffff; + cursor: pointer; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.primary-btn:hover, +.secondary-btn:hover { + opacity: 0.92; +} + +.primary-btn:active, +.secondary-btn:active { + transform: scale(0.98); +} + +.primary-btn { + background: #223f6b; + min-width: 8.9rem; +} + +.secondary-btn { + background: #667a99; + min-width: 11rem; +} + +/* Responsive */ +@media (max-width: 75rem) { + .profile-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 62rem) { + .sidebar { + display: none; + } + + .topbar { + padding: 0 1rem; + } + + .search-box { + width: 16rem; + } + + .page-content { + padding: 1.5rem 1rem; + } + + .page-header h1 { + font-size: 2rem; + } +} \ No newline at end of file diff --git a/Admin/profile/admin-profile.html b/Admin/profile/admin-profile.html new file mode 100644 index 0000000..4c853b8 --- /dev/null +++ b/Admin/profile/admin-profile.html @@ -0,0 +1,143 @@ + + + + + + AIDLoop - Admin Profile + + + + +
    + + + + +
    + +
    + + +
    +
    + + +
    + + Admin Avatar +
    +
    + + +
    + + +
    + +
    +

    Profile Information

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    + + +
    +

    Security

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/Admin/profile/admin-profile.js b/Admin/profile/admin-profile.js new file mode 100644 index 0000000..8878068 --- /dev/null +++ b/Admin/profile/admin-profile.js @@ -0,0 +1,136 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + fullName: document.getElementById("fullName"), + email: document.getElementById("emailAddress"), + role: document.getElementById("role"), + phoneNumber: document.getElementById("phoneNumber"), + editBtn: document.getElementById("editProfileBtn"), + profileForm: document.getElementById("profileForm"), + passwordForm: document.getElementById("passwordForm") +}; + +let isEditing = false; + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + data?.message || data?.error || "Request failed" + ); + } + + return data; +} + +function setInputsEditable(editable) { + elements.phoneNumber.readOnly = !editable; +} + +function populateProfile(profile) { + elements.fullName.value = + profile.fullName || profile.name || ""; + + elements.email.value = profile.email || ""; + + elements.role.value = + profile.role + ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1) + : "Admin"; + + elements.phoneNumber.value = + profile.phoneNumber || + profile.phone || + ""; +} + +async function loadProfile() { + try { + let profile; + + try { + profile = await apiRequest("/user/me"); + } catch { + profile = await apiRequest("/users/me"); + } + + populateProfile(profile); + } catch (error) { + console.error("Failed to load profile:", error.message); + window.location.href = "../login/admin-login.html"; + } +} + +async function updateProfile() { + try { + const payload = { + phoneNumber: elements.phoneNumber.value + }; + + await apiRequest("/user/me", { + method: "PUT", + body: JSON.stringify(payload) + }); + + alert("Profile updated successfully ✅"); + } catch (error) { + alert(error.message); + } +} + +function toggleEditMode() { + isEditing = !isEditing; + + setInputsEditable(isEditing); + + elements.editBtn.textContent = isEditing + ? "Save Changes" + : "Edit Profile"; + + if (!isEditing) { + updateProfile(); + } +} + +function handlePasswordChange(e) { + e.preventDefault(); + + const current = document.getElementById("currentPassword").value; + const newPass = document.getElementById("newPassword").value; + const confirm = document.getElementById("confirmPassword").value; + + if (!current || !newPass || !confirm) { + alert("All fields are required"); + return; + } + + if (newPass !== confirm) { + alert("Passwords do not match"); + return; + } + + // ⚠️ No backend endpoint provided for password change + alert("Password change endpoint not implemented yet"); +} + +document.addEventListener("DOMContentLoaded", async () => { + setInputsEditable(false); + + elements.editBtn.addEventListener("click", toggleEditMode); + elements.passwordForm.addEventListener("submit", handlePasswordChange); + + await loadProfile(); +}); \ No newline at end of file diff --git a/Admin/user/user-details.css b/Admin/user/user-details.css new file mode 100644 index 0000000..afbbe68 --- /dev/null +++ b/Admin/user/user-details.css @@ -0,0 +1,234 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + min-height: 100vh; + background: #edf1f5; +} + +.page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} + +.details-card { + width: 100%; + max-width: 33rem; + background: #ffffff; + border-radius: 1.5rem; + box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.12); + padding: 2.5rem 1.4rem 1.6rem; +} + +.header-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 3rem; +} + +.header-row h1 { + font-size: 1.05rem; + font-weight: 500; + color: #172033; + line-height: 1.2; +} + +.role-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 10rem; + padding: 0.95rem 1.2rem; + border-radius: 1rem; + color: #ffffff; + font-size: 0.95rem; + font-weight: 700; +} + +.role-badge.organizer { + background: #c536af; +} + +.role-badge.volunteer { + background: #1d7de2; +} + +.role-badge.admin { + background: #223f6b; +} + +.info-grid { + display: grid; + grid-template-columns: 1fr 1.2fr; + column-gap: 1.5rem; + row-gap: 2.35rem; + padding: 0 1.45rem; + margin-bottom: 1.8rem; +} + +.label { + font-size: 0.95rem; + font-weight: 400; + color: #2a3342; +} + +.value { + font-size: 0.95rem; + font-weight: 500; + color: #172033; + line-height: 1.2; + word-break: break-word; +} + +.status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 8rem; + padding: 0.95rem 1rem; + border-radius: 0.6rem; + color: #ffffff; + font-size: 0.95rem; + font-weight: 500; +} + +.status-badge.active { + background: #16a357; +} + +.status-badge.inactive { + background: #ef2b2b; +} + +.description-section { + margin-top: 1rem; + margin-bottom: 1.5rem; + padding: 0 0.3rem; +} + +.description-section h2 { + font-size: 1.05rem; + font-weight: 400; + color: #172033; + margin-bottom: 1rem; +} + +.description-box { + border: 0.08rem solid #2f4f7e; + border-radius: 1rem; + padding: 1.35rem 1.15rem; + min-height: 9.2rem; + color: #8b8f96; + font-size: 0.95rem; + line-height: 1.35; +} + +.feedback { + min-height: 1.2rem; + margin: 0.5rem 0 1rem; + text-align: center; + font-size: 0.9rem; +} + +.feedback.success { + color: #15803d; +} + +.feedback.error { + color: #dc2626; +} + +.action-row { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0 0.3rem; +} + +.action-btn { + border: none; + border-radius: 0.55rem; + min-width: 9rem; + padding: 1rem 1.2rem; + font-size: 0.95rem; + font-weight: 500; + color: #ffffff; + cursor: pointer; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.action-btn:hover { + opacity: 0.92; +} + +.action-btn:active { + transform: scale(0.98); +} + +.action-btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.deactivate-btn { + background: #ef2b2b; +} + +.reactivate-btn { + background: #16a357; +} + +@media (max-width: 34rem) { + .details-card { + padding: 2rem 1rem 1.2rem; + } + + .header-row { + flex-direction: column; + align-items: flex-start; + margin-bottom: 2rem; + } + + .role-badge { + width: 100%; + min-width: auto; + } + + .info-grid { + grid-template-columns: 1fr; + row-gap: 0.75rem; + padding: 0; + } + + .label { + font-weight: 600; + margin-top: 0.75rem; + } + + .status-badge { + width: 100%; + min-width: auto; + } + + .description-section { + padding: 0; + } + + .action-row { + flex-direction: column; + padding: 0; + } + + .action-btn { + width: 100%; + } +} \ No newline at end of file diff --git a/Admin/user/user-details.html b/Admin/user/user-details.html new file mode 100644 index 0000000..5092262 --- /dev/null +++ b/Admin/user/user-details.html @@ -0,0 +1,56 @@ + + + + + + User Details + + + +
    +
    +
    +

    Bacaris Climate Project

    + Organizer +
    + +
    +
    Org. Name:
    +
    Bacaris Climate Project
    + +
    Email:
    +
    bacaris@bacaris.org
    + +
    Phone No:
    +
    08123456789
    + +
    Location:
    +
    Lagos
    + +
    Date Joined:
    +
    Jan 10, 2026
    + +
    Status
    +
    + Active +
    +
    + +
    +

    Description

    +
    + Bacaris Climate Project is focused on coastal cleanups and environmental awareness in Lagos. + They organize monthly volunteer events and partner with local communities to improve sustainability +
    +
    + +
    + + +
    +
    +
    + + + + \ No newline at end of file diff --git a/Admin/user/user-details.js b/Admin/user/user-details.js new file mode 100644 index 0000000..d13e623 --- /dev/null +++ b/Admin/user/user-details.js @@ -0,0 +1,227 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + userTitle: document.getElementById("userTitle"), + roleBadge: document.getElementById("roleBadge"), + nameLabel: document.getElementById("nameLabel"), + displayName: document.getElementById("displayName"), + email: document.getElementById("email"), + phoneNumber: document.getElementById("phoneNumber"), + location: document.getElementById("location"), + dateJoined: document.getElementById("dateJoined"), + statusBadge: document.getElementById("statusBadge"), + description: document.getElementById("description"), + feedback: document.getElementById("feedback"), + deactivateBtn: document.getElementById("deactivateBtn"), + reactivateBtn: document.getElementById("reactivateBtn") +}; + +let currentUser = null; +let userId = null; + +function getUserIdFromURL() { + const params = new URLSearchParams(window.location.search); + return params.get("id"); +} + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeUsers(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.users)) return payload.users; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function getDisplayName(user) { + return user.fullName || user.name || user.organizationName || "Unnamed User"; +} + +function getRole(user) { + const role = String(user.role || "user").toLowerCase(); + if (role === "organizer") return "Organizer"; + if (role === "volunteer") return "Volunteer"; + if (role === "admin") return "Admin"; + return role.charAt(0).toUpperCase() + role.slice(1); +} + +function getRoleClass(user) { + const role = String(user.role || "").toLowerCase(); + if (role === "organizer") return "organizer"; + if (role === "admin") return "admin"; + return "volunteer"; +} + +function isUserActive(user) { + if (typeof user.isActive === "boolean") return user.isActive; + const status = String(user.status || "").toLowerCase(); + return status !== "deactivated" && status !== "inactive"; +} + +function getLocation(user) { + if (typeof user.location === "string" && user.location.trim()) { + return user.location; + } + + if (user.location && typeof user.location === "object") { + return ( + user.location.city || + user.location.state || + user.location.venue || + "—" + ); + } + + return user.city || user.state || "—"; +} + +function formatDate(dateValue) { + if (!dateValue) return "—"; + const date = new Date(dateValue); + if (Number.isNaN(date.getTime())) return dateValue; + + return date.toLocaleDateString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric" + }); +} + +function setStatusBadge(user) { + const active = isUserActive(user); + elements.statusBadge.className = `status-badge ${active ? "active" : "inactive"}`; + elements.statusBadge.textContent = active ? "Active" : "Inactive"; +} + +function renderUser(user) { + currentUser = user; + + const displayName = getDisplayName(user); + const roleText = getRole(user); + const roleClass = getRoleClass(user); + const isOrganizer = roleClass === "organizer"; + + elements.userTitle.textContent = displayName; + elements.roleBadge.textContent = roleText; + elements.roleBadge.className = `role-badge ${roleClass}`; + + elements.nameLabel.textContent = isOrganizer ? "Org. Name:" : "Full Name:"; + elements.displayName.textContent = displayName; + elements.email.textContent = user.email || "—"; + elements.phoneNumber.textContent = + user.phoneNumber || user.phone || user.mobile || "—"; + elements.location.textContent = getLocation(user); + elements.dateJoined.textContent = formatDate(user.createdAt || user.dateJoined); + elements.description.textContent = + user.description || + user.bio || + (isOrganizer + ? "No organization description available." + : "No user description available."); + + setStatusBadge(user); + + if (!isUserActive(user)) { + elements.deactivateBtn.disabled = true; + elements.deactivateBtn.textContent = "Deactivated"; + } else { + elements.deactivateBtn.disabled = false; + elements.deactivateBtn.textContent = "Deactivate"; + } +} + +function setLoadingState(isLoading) { + elements.deactivateBtn.disabled = isLoading; + elements.reactivateBtn.disabled = isLoading; + elements.deactivateBtn.textContent = isLoading ? "Processing..." : (currentUser && !isUserActive(currentUser) ? "Deactivated" : "Deactivate"); + elements.reactivateBtn.textContent = isLoading ? "Processing..." : "Refresh"; +} + +async function loadUserDetails() { + userId = getUserIdFromURL(); + + if (!userId) { + elements.feedback.textContent = "User ID not found in URL."; + elements.feedback.classList.add("error"); + return; + } + + try { + const usersPayload = await apiRequest("/user"); + const users = normalizeUsers(usersPayload); + + const user = users.find( + (item) => String(item._id || item.id) === String(userId) + ); + + if (!user) { + throw new Error("User not found."); + } + + renderUser(user); + } catch (error) { + elements.feedback.textContent = error.message; + elements.feedback.classList.add("error"); + } +} + +async function deactivateUser() { + if (!currentUser || !isUserActive(currentUser)) { + elements.feedback.textContent = "This user is already deactivated."; + elements.feedback.className = "feedback error"; + return; + } + + const confirmed = window.confirm("Deactivate this user?"); + if (!confirmed) return; + + try { + setLoadingState(true); + elements.feedback.textContent = ""; + + await apiRequest(`/admin/users/${userId}/deactivate`, { + method: "PATCH" + }); + + currentUser.isActive = false; + currentUser.status = "deactivated"; + renderUser(currentUser); + + elements.feedback.textContent = "User deactivated successfully."; + elements.feedback.className = "feedback success"; + } catch (error) { + elements.feedback.textContent = error.message; + elements.feedback.className = "feedback error"; + } finally { + setLoadingState(false); + } +} + +elements.deactivateBtn.addEventListener("click", deactivateUser); +elements.reactivateBtn.addEventListener("click", loadUserDetails); + +document.addEventListener("DOMContentLoaded", loadUserDetails); \ No newline at end of file diff --git a/Admin/user/user-management.css b/Admin/user/user-management.css new file mode 100644 index 0000000..4ed72e8 --- /dev/null +++ b/Admin/user/user-management.css @@ -0,0 +1,313 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + background: #eef3f7; + color: #172033; +} + +.dashboard { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 15rem; + background: #223f6b; + color: #ffffff; + padding: 2rem 0.9rem; +} + +.sidebar-logo { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + margin-bottom: 2.8rem; +} + +.sidebar-logo img { + width: 3.1rem; + height: 3.1rem; + object-fit: contain; +} + +.sidebar-logo h2 { + font-size: 1.25rem; + font-weight: 700; + color: #1d7de2; +} + +.sidebar-logo h2 span { + color: #27a657; +} + +.sidebar-menu { + list-style: none; +} + +.sidebar-menu li { + margin-bottom: 0.85rem; +} + +.sidebar-menu li a { + display: flex; + align-items: center; + gap: 0.95rem; + text-decoration: none; + color: #ffffff; + padding: 0.95rem 0.9rem; + border-radius: 0.45rem; + font-size: 0.98rem; + transition: background 0.2s ease; +} + +.sidebar-menu li a i { + width: 1rem; + text-align: center; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover { + background: #1d7de2; +} + +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.topbar { + height: 6rem; + background: #ffffff; + border-bottom: 0.0625rem solid #e6e8eb; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1.9rem; +} + +.search-box { + width: 21rem; + height: 3rem; + display: flex; + align-items: center; + gap: 0.8rem; + border: 0.0625rem solid #d9dde3; + border-radius: 0.4rem; + padding: 0 0.95rem; + background: #ffffff; +} + +.search-box i { + color: #6b7280; +} + +.search-box input { + border: none; + outline: none; + width: 100%; + background: transparent; + font-size: 1rem; + color: #374151; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 1.2rem; +} + +.notification { + position: relative; + color: #111827; + font-size: 1.35rem; +} + +.notification-dot { + position: absolute; + top: 0.1rem; + right: -0.12rem; + width: 0.48rem; + height: 0.48rem; + background: #ef2f2f; + border-radius: 50%; +} + +.user-avatar { + width: 2.65rem; + height: 2.65rem; + border-radius: 50%; + object-fit: cover; +} + +.page-content { + padding: 2rem 1.9rem; +} + +.page-header { + margin-bottom: 2rem; +} + +.page-header h1 { + font-size: 2.5rem; + line-height: 1.1; + font-weight: 700; + margin-bottom: 0.45rem; +} + +.page-header p { + font-size: 1rem; + color: #6b7280; +} + +.table-wrapper { + background: #ffffff; + border: 0.0625rem solid #e3e7eb; + border-radius: 0.45rem; + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #eef1f4; +} + +th, +td { + text-align: left; + padding: 1.35rem 1.2rem; +} + +th { + font-size: 0.8rem; + letter-spacing: 0.08em; + color: #7a8290; + font-weight: 600; +} + +td { + font-size: 1rem; + color: #172033; + border-top: 0.0625rem solid #edf0f2; + vertical-align: middle; +} + +.role-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 6.5rem; + padding: 0.5rem 0.85rem; + border-radius: 0.55rem; + font-size: 0.78rem; + font-weight: 700; + color: #ffffff; +} + +.role-badge.organizer { + background: #c33bb1; +} + +.role-badge.volunteer { + background: #1d7de2; +} + +.actions-cell { + display: flex; + gap: 0.7rem; + align-items: center; +} + +.btn { + border: none; + border-radius: 0.35rem; + padding: 0.75rem 1.2rem; + font-size: 0.9rem; + cursor: pointer; + color: #ffffff; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.btn:hover { + opacity: 0.92; +} + +.btn:active { + transform: scale(0.98); +} + +.btn-view { + background: #6b7280; + min-width: 4.4rem; +} + +.btn-active { + background: #16a34a; + min-width: 5.1rem; +} + +.btn-deactivate { + background: #ef2f2f; + min-width: 6.2rem; +} + +.admin-profile { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.admin-text h4 { + font-size: 0.85rem; + font-weight: 600; + color: #172033; + line-height: 1.2; +} + +.admin-text p { + font-size: 0.75rem; + color: #6b7280; + line-height: 1.2; +} + +@media (max-width: 75rem) { + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 58rem; + } +} + +@media (max-width: 62rem) { + .sidebar { + display: none; + } + + .topbar { + padding: 0 1rem; + } + + .search-box { + width: 16rem; + } + + .page-content { + padding: 1.5rem 1rem; + } + + .page-header h1 { + font-size: 2rem; + } +} \ No newline at end of file diff --git a/Admin/user/user-management.html b/Admin/user/user-management.html new file mode 100644 index 0000000..b9000de --- /dev/null +++ b/Admin/user/user-management.html @@ -0,0 +1,165 @@ + + + + + + AIDLoop - User Management + + + + +
    + + +
    +
    + + +
    +
    + + +
    + +
    + Admin Avatar +
    +

    Loading...

    +

    Admin

    +
    +
    +
    +
    + +
    + + +
    + + + + + + + + + + + + + + + + +
    USER NAMEUSER EMAILLOCATIONROLEACTIONS
    Loading users...
    +
    +
    +
    +
    + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/user/user-management.js b/Admin/user/user-management.js new file mode 100644 index 0000000..7dce520 --- /dev/null +++ b/Admin/user/user-management.js @@ -0,0 +1,228 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + searchInput: document.getElementById("searchInput"), + userTable: document.getElementById("userTable"), + adminName: document.getElementById("adminName"), + adminRole: document.getElementById("adminRole"), + adminAvatar: document.getElementById("adminAvatar") +}; + +let allUsers = []; + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeUsers(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.users)) return payload.users; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function getDisplayName(user) { + return user.fullName || user.name || user.organizationName || "Unnamed User"; +} + +function getLocation(user) { + if (typeof user.location === "string" && user.location.trim()) { + return user.location; + } + + if (user.location && typeof user.location === "object") { + return user.location.city || user.location.state || user.location.venue || "—"; + } + + return user.city || user.state || "—"; +} + +function getRole(user) { + const role = String(user.role || "user").toLowerCase(); + if (role === "organizer") return "Organizer"; + if (role === "volunteer") return "Volunteer"; + if (role === "admin") return "Admin"; + return role.charAt(0).toUpperCase() + role.slice(1); +} + +function getRoleClass(user) { + return String(user.role || "").toLowerCase() === "organizer" + ? "organizer" + : "volunteer"; +} + +function isUserActive(user) { + if (typeof user.isActive === "boolean") return user.isActive; + const status = String(user.status || "").toLowerCase(); + return status !== "deactivated" && status !== "inactive"; +} + +function renderUsers() { + const query = elements.searchInput.value.trim().toLowerCase(); + + const filteredUsers = allUsers.filter((user) => { + const searchable = ` + ${getDisplayName(user)} + ${user.email || ""} + ${getLocation(user)} + ${getRole(user)} + `.toLowerCase(); + + return searchable.includes(query); + }); + + if (!filteredUsers.length) { + elements.userTable.innerHTML = ` + + No users found. + + `; + return; + } + + elements.userTable.innerHTML = filteredUsers + .map((user) => { + const id = user._id || user.id || ""; + const active = isUserActive(user); + + return ` + + ${getDisplayName(user)} + ${user.email || "—"} + ${getLocation(user)} + ${getRole(user)} + + + + + + `; + }) + .join(""); + + bindActionButtons(); +} + +function bindActionButtons() { + document.querySelectorAll(".btn-view").forEach((button) => { + button.addEventListener("click", () => { + const userId = button.dataset.id; + window.location.href = `user-details.html?id=${encodeURIComponent(userId)}`; + }); + }); + + document.querySelectorAll(".action-status-btn").forEach((button) => { + button.addEventListener("click", async () => { + const userId = button.dataset.id; + const isActive = button.dataset.active === "true"; + + if (!isActive) { + alert("This user is already deactivated."); + return; + } + + const confirmed = window.confirm("Deactivate this user?"); + if (!confirmed) return; + + try { + button.disabled = true; + button.textContent = "Processing..."; + + await apiRequest(`/admin/users/${userId}/deactivate`, { + method: "PATCH" + }); + + const userIndex = allUsers.findIndex( + (user) => String(user._id || user.id) === String(userId) + ); + + if (userIndex > -1) { + allUsers[userIndex].isActive = false; + allUsers[userIndex].status = "deactivated"; + } + + renderUsers(); + } catch (error) { + alert(error.message); + renderUsers(); + } + }); + }); +} + +async function loadAdminProfile() { + try { + let profile; + + try { + profile = await apiRequest("/users/me"); + } catch { + profile = await apiRequest("/user/me"); + } + + elements.adminName.textContent = profile.fullName || profile.name || "Admin"; + elements.adminRole.textContent = profile.role + ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1) + : "Admin"; + + if (profile.profileImage) { + elements.adminAvatar.src = profile.profileImage; + } + } catch (error) { + console.error("Failed to load admin profile:", error.message); + window.location.href = "../login/admin-login.html"; + } +} + +async function loadUsers() { + try { + const usersPayload = await apiRequest("/user"); + allUsers = normalizeUsers(usersPayload); + renderUsers(); + } catch (error) { + console.error("Failed to load users:", error.message); + elements.userTable.innerHTML = ` + + Failed to load users. + + `; + } +} + +function bindSearch() { + elements.searchInput.addEventListener("input", renderUsers); +} + +document.addEventListener("DOMContentLoaded", async () => { + bindSearch(); + await loadAdminProfile(); + await loadUsers(); +}); \ No newline at end of file diff --git a/Admin/verification/verification-details.css b/Admin/verification/verification-details.css new file mode 100644 index 0000000..5eb888a --- /dev/null +++ b/Admin/verification/verification-details.css @@ -0,0 +1,190 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", sans-serif; +} + +body { + min-height: 100vh; + background: #eef1f6; + display: flex; + justify-content: center; + align-items: center; +} + +.page { + width: 100%; + max-width: 40rem; + padding: 1rem; +} + +.card { + background: #ffffff; + padding: 2rem; + border-radius: 1rem; + box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.1); +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + margin-bottom: 2rem; +} + +.card-header h1 { + font-size: 1.3rem; + color: #1f2937; + font-weight: 600; +} + +.status-badge { + padding: 0.7rem 1.2rem; + border-radius: 0.8rem; + color: #fff; + font-weight: 600; + font-size: 0.9rem; +} + +.status-badge.awaiting { + background: #f2a10d; +} + +.status-badge.approved { + background: #18a957; +} + +.status-badge.rejected { + background: #ef2b2b; +} + +.details-grid { + display: grid; + grid-template-columns: 1fr 1.5fr; + gap: 1rem 2rem; + margin-bottom: 2rem; +} + +.label { + color: #6b7280; + font-weight: 500; +} + +.value { + color: #111827; + font-weight: 500; + word-break: break-word; +} + +.value a { + color: #1f2937; + text-decoration: underline; +} + +.description-section h2 { + margin-bottom: 1rem; + font-size: 1.1rem; + color: #1f2937; +} + +.description-box { + border: 0.08rem solid #35527a; + border-radius: 0.8rem; + padding: 1rem; + min-height: 7rem; + color: #6b7280; + line-height: 1.5; + margin-bottom: 2rem; +} + +.actions { + display: flex; + justify-content: space-between; + gap: 1rem; +} + +.btn { + border: none; + padding: 0.9rem 2rem; + border-radius: 0.5rem; + color: #fff; + cursor: pointer; + font-size: 1rem; + font-weight: 600; +} + +.btn-reject { + background: #ef2b2b; +} + +.btn-approve { + background: #18a957; +} + +.btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.feedback { + margin-top: 1rem; + text-align: center; + font-size: 0.95rem; +} + +.feedback.success { + color: #15803d; +} + +.feedback.error { + color: #dc2626; +} + +@media (max-width: 36rem) { + .card-header, + .actions, + .details-grid { + grid-template-columns: 1fr; + flex-direction: column; + align-items: stretch; + } + + .details-grid { + display: grid; + gap: 0.6rem; + } + + .btn { + width: 100%; + } +} + + + + + + +/* .auth-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.auth-card { + width: 100%; + max-width: 32rem; + background: #fff; + padding: 2rem; + border-radius: 1rem; + box-shadow: 0 0.5rem 2rem rgba(0,0,0,0.06); +} + +.actions { + display: flex; + gap: 1rem; + margin-top: 1rem; +} */ diff --git a/Admin/verification/verification-details.html b/Admin/verification/verification-details.html new file mode 100644 index 0000000..fbac542 --- /dev/null +++ b/Admin/verification/verification-details.html @@ -0,0 +1,71 @@ + + + + + + Verification Details + + + +
    +
    +
    +

    Loading...

    + Awaiting Verification +
    + +
    +
    Org. Name:
    +
    Loading...
    + +
    Social Links:
    + + +
    Email:
    +
    + +
    Phone No:
    +
    + +
    Location:
    +
    +
    + +
    +

    Description

    +
    + Loading organization description... +
    +
    + +
    + + +
    + + +
    +
    + + + + + + + + + + + + + + diff --git a/Admin/verification/verification-details.js b/Admin/verification/verification-details.js new file mode 100644 index 0000000..8c6dd24 --- /dev/null +++ b/Admin/verification/verification-details.js @@ -0,0 +1,303 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const els = { + orgTitle: document.getElementById("orgTitle"), + orgName: document.getElementById("orgName"), + socialLinks: document.getElementById("socialLinks"), + email: document.getElementById("email"), + phoneNumber: document.getElementById("phoneNumber"), + location: document.getElementById("location"), + description: document.getElementById("description"), + statusBadge: document.getElementById("statusBadge"), + rejectBtn: document.getElementById("rejectBtn"), + approveBtn: document.getElementById("approveBtn"), + feedback: document.getElementById("feedback") +}; + +let organizerId = null; + +function getOrganizerIdFromURL() { + const params = new URLSearchParams(window.location.search); + return params.get("id"); +} + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeUsers(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.users)) return payload.users; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function detectStatus(user) { + const status = String(user.status || "").toLowerCase(); + const approvalStatus = String(user.approvalStatus || "").toLowerCase(); + const isVerified = Boolean(user.isVerified); + + if (status === "rejected" || approvalStatus === "rejected") return "rejected"; + + if ( + status === "verified" || + status === "approved" || + approvalStatus === "verified" || + approvalStatus === "approved" || + isVerified + ) { + return "approved"; + } + + return "awaiting"; +} + +function getLocationText(user) { + if (typeof user.location === "string" && user.location.trim()) { + return user.location; + } + + if (user.location && typeof user.location === "object") { + return user.location.city || user.location.venue || user.location.state || "—"; + } + + return user.city || user.state || "—"; +} + +function renderSocialLinks(user) { + const links = user.socialLinks || user.website || user.socialLink || user.link || ""; + + if (!links) { + els.socialLinks.textContent = "—"; + return; + } + + if (Array.isArray(links)) { + els.socialLinks.innerHTML = links + .map(link => `${link}`) + .join("
    "); + return; + } + + els.socialLinks.innerHTML = `${links}`; +} + +function setBadge(status) { + els.statusBadge.className = "status-badge"; + + if (status === "approved") { + els.statusBadge.classList.add("approved"); + els.statusBadge.textContent = "Approved"; + return; + } + + if (status === "rejected") { + els.statusBadge.classList.add("rejected"); + els.statusBadge.textContent = "Rejected"; + return; + } + + els.statusBadge.classList.add("awaiting"); + els.statusBadge.textContent = "Awaiting Verification"; +} + +function renderOrganizer(user) { + const displayName = user.fullName || user.name || user.organizationName || "Unnamed Organizer"; + const currentStatus = detectStatus(user); + + els.orgTitle.textContent = displayName; + els.orgName.textContent = displayName; + els.email.textContent = user.email || "—"; + els.phoneNumber.textContent = user.phoneNumber || user.phone || user.mobile || "—"; + els.location.textContent = getLocationText(user); + els.description.textContent = user.description || user.bio || "No description available."; + + renderSocialLinks(user); + setBadge(currentStatus); + + if (currentStatus === "approved" || currentStatus === "rejected") { + els.approveBtn.disabled = true; + els.rejectBtn.disabled = true; + } +} + +function setLoadingState(isLoading) { + els.approveBtn.disabled = isLoading; + els.rejectBtn.disabled = isLoading; + + els.approveBtn.textContent = isLoading ? "Processing..." : "Approve"; + els.rejectBtn.textContent = isLoading ? "Processing..." : "Reject"; +} + +async function loadOrganizer() { + organizerId = getOrganizerIdFromURL(); + + if (!organizerId) { + els.feedback.textContent = "Organizer ID not found in URL."; + els.feedback.classList.add("error"); + return; + } + + try { + let usersPayload; + + try { + usersPayload = await apiRequest("/user"); + } catch { + usersPayload = await apiRequest("/users"); + } + + const users = normalizeUsers(usersPayload); + const organizer = users.find( + user => String(user._id || user.id) === String(organizerId) + ); + + if (!organizer) { + throw new Error("Organizer not found."); + } + + renderOrganizer(organizer); + } catch (error) { + els.feedback.textContent = error.message; + els.feedback.classList.add("error"); + } +} + +async function approveOrganizer() { + try { + setLoadingState(true); + els.feedback.textContent = ""; + + await apiRequest(`/admin/organizers/${organizerId}/approve`, { + method: "PATCH" + }); + + setBadge("approved"); + els.feedback.textContent = "Organizer approved successfully."; + els.feedback.classList.remove("error"); + els.feedback.classList.add("success"); + + setTimeout(() => { + window.location.href = "verification-queue.html"; + }, 1000); + } catch (error) { + els.feedback.textContent = error.message; + els.feedback.classList.remove("success"); + els.feedback.classList.add("error"); + } finally { + setLoadingState(false); + } +} + +async function rejectOrganizer() { + try { + setLoadingState(true); + els.feedback.textContent = ""; + + await apiRequest(`/admin/organizers/${organizerId}/reject`, { + method: "PATCH" + }); + + setBadge("rejected"); + els.feedback.textContent = "Organizer rejected successfully."; + els.feedback.classList.remove("error"); + els.feedback.classList.add("success"); + + setTimeout(() => { + window.location.href = "verification-queue.html"; + }, 1000); + } catch (error) { + els.feedback.textContent = error.message; + els.feedback.classList.remove("success"); + els.feedback.classList.add("error"); + } finally { + setLoadingState(false); + } +} + +els.approveBtn.addEventListener("click", approveOrganizer); +els.rejectBtn.addEventListener("click", rejectOrganizer); + +document.addEventListener("DOMContentLoaded", loadOrganizer); + + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { getQueryParam, getLocationText } from "../../assets/js/utils.js"; + +// const id = getQueryParam("id"); +// const orgName = document.getElementById("orgName"); +// const orgEmail = document.getElementById("orgEmail"); +// const orgLocation = document.getElementById("orgLocation"); +// const feedback = document.getElementById("feedback"); +// let organizer; + +// async function load() { +// const payload = await apiRequest("/user"); +// const users = normalizeArray(payload, ["users"]); +// organizer = users.find((u) => String(u._id || u.id) === String(id)); +// orgName.textContent = organizer?.fullName || organizer?.name || organizer?.organizationName || "Organizer"; +// orgEmail.textContent = organizer?.email || "—"; +// orgLocation.textContent = getLocationText(organizer); +// } + +// document.getElementById("approveBtn").addEventListener("click", async () => { +// try { +// await apiRequest(`/admin/organizers/${id}/approve`, { method: "PATCH" }); +// feedback.textContent = "Organizer approved successfully."; +// feedback.className = "success-message"; +// setTimeout(() => { window.location.href = ROUTES.adminVerificationQueue; }, 700); +// } catch (err) { +// feedback.textContent = err.message || "Failed to approve organizer."; +// feedback.className = "form-error"; +// } +// }); + +// document.getElementById("rejectBtn").addEventListener("click", async () => { +// try { +// await apiRequest(`/admin/organizers/${id}/reject`, { method: "PATCH" }); +// feedback.textContent = "Organizer rejected successfully."; +// feedback.className = "success-message"; +// setTimeout(() => { window.location.href = ROUTES.adminVerificationQueue; }, 700); +// } catch (err) { +// feedback.textContent = err.message || "Failed to reject organizer."; +// feedback.className = "form-error"; +// } +// }); + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireAdmin(); +// await load(); +// }); diff --git a/Admin/verification/verification-queue.css b/Admin/verification/verification-queue.css new file mode 100644 index 0000000..0a6a688 --- /dev/null +++ b/Admin/verification/verification-queue.css @@ -0,0 +1,255 @@ +*{ + margin:0; + padding:0; + box-sizing:border-box; + font-family:Segoe UI; +} + +body{ + background:#f4f6f9; +} + +.dashboard{ + display:flex; +} + +/* SIDEBAR */ + +.sidebar{ + width:16rem; + background:#243a5e; + color:white; + min-height:100vh; + padding:2rem 1.5rem; +} + +.logo{ + text-align:center; + margin-bottom:2rem; +} + +.logo img{ + width:3rem; +} + +.menu{ + list-style:none; +} + +.menu li{ + padding:0.9rem 1rem; + border-radius:0.5rem; + margin-bottom:0.5rem; + cursor:pointer; +} + +.menu li.active{ + background:#1f6ed4; +} + +.menu li:hover{ + background:#1f6ed4; +} + +/* CONTENT */ + +.content{ + flex:1; + padding:2rem; +} + +/* TOPBAR */ + +.topbar{ + display:flex; + justify-content:space-between; + margin-bottom:2rem; +} + +.search{ + display:flex; + align-items:center; + background:white; + padding:0.6rem 1rem; + border-radius:0.5rem; + gap:0.5rem; + width:20rem; +} + +.search input{ + border:none; + outline:none; + width:100%; +} + +.top-icons{ + display:flex; + align-items:center; + gap:1rem; +} + +.top-icons img{ + border-radius:50%; +} + +/* HEADER */ + +.page-header{ + display:flex; + justify-content:space-between; + align-items:center; + margin-bottom:2rem; +} + +.page-header h1{ + font-size:2rem; +} + +.page-header h3{ + font-size:1rem; +} + +.page-header p{ + color:#6b7280; +} + +/* FILTER BUTTONS */ + +.filter-buttons{ + display: flex; + flex-wrap: wrap; + gap: 0.3rem; +} + +.filter-btn { + padding:0.5rem 1rem; + border-radius:0.4rem; + border:1px solid #ddd; + background:white; + cursor:pointer; + margin-left:0.5rem; +} + +.filter-btn.active{ + background:#1f6ed4; + color:white; +} + +/* TABLE */ + +.table-card{ + background:white; + border-radius:0.6rem; + padding:1rem; +} + +table{ + width:100%; + border-collapse:collapse; +} + +th, td{ + text-align:left; + padding:1rem; + border-bottom:1px solid #eee; +} + +th{ + color:#6b7280; +} + +/* BADGES */ + +.badge{ + padding:0.3rem 0.8rem; + border-radius:1rem; + font-size:0.8rem; + font-weight:600; +} + +.awaiting{ + background:#f59e0b; + color:white; +} + +.verified{ + background:#16a34a; + color:white; +} + +.rejected{ + background:#ef4444; + color:white; +} + +/* ACTION BUTTONS */ + +button{ + border:none; + padding:0.5rem 1rem; + border-radius:0.4rem; + cursor:pointer; +} + +.approve{ + background:#16a34a; + color:white; +} + +.reject{ + background:#ef4444; + color:white; +} + +.view{ + background:#ddd; +} + +.menu li a { + display: flex; + align-items: center; + gap: 0.6rem; + color: inherit; + text-decoration: none; +} + +.admin-mini-profile { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.admin-mini-profile img { + border-radius: 50%; +} + +.admin-mini-profile span { + font-size: 0.9rem; + font-weight: 600; +} + +.badge.awaiting { + background: #f59e0b; + color: #fff; +} + +.badge.verified { + background: #16a34a; + color: #fff; +} + +.badge.rejected { + background: #dc2626; + color: #fff; +} + + + + + +/* uses shared css */ + + + + + diff --git a/Admin/verification/verification-queue.html b/Admin/verification/verification-queue.html new file mode 100644 index 0000000..8af89db --- /dev/null +++ b/Admin/verification/verification-queue.html @@ -0,0 +1,114 @@ + + + + + + AIDLoop Verification + + + + +
    + + + + +
    + !-- TOPBAR -- +
    + + +
    + +
    + Admin Avatar + Loading... +
    +
    +
    + + + + + +
    + + + + + + + + + + + + + + + + +
    Organization NameContact EmailLocationStatusActions
    Loading verification queue...
    +
    +
    +
    + + + + + + + + + + + + + + + diff --git a/Admin/verification/verification-queue.js b/Admin/verification/verification-queue.js new file mode 100644 index 0000000..965ba95 --- /dev/null +++ b/Admin/verification/verification-queue.js @@ -0,0 +1,282 @@ +const API_BASE_URL = "https://aidloop-backend.onrender.com/api"; + +const elements = { + searchInput: document.getElementById("searchInput"), + orgTable: document.getElementById("orgTable"), + pendingCount: document.getElementById("pendingCount"), + adminName: document.getElementById("adminName"), + adminAvatar: document.getElementById("adminAvatar"), + filterButtons: document.querySelectorAll(".filter-btn") +}; + +let organizers = []; +let currentFilter = "awaiting"; + +async function apiRequest(endpoint, options = {}) { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + ...(options.headers || {}) + }, + ...options + }); + + const contentType = response.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await response.json() + : await response.text(); + + if (!response.ok) { + throw new Error( + (data && data.message) || + (data && data.error) || + "Request failed" + ); + } + + return data; +} + +function normalizeUsers(payload) { + if (Array.isArray(payload)) return payload; + if (Array.isArray(payload?.users)) return payload.users; + if (Array.isArray(payload?.data)) return payload.data; + return []; +} + +function getVerificationStatus(user) { + const status = String(user.status || "").toLowerCase(); + const approvalStatus = String(user.approvalStatus || "").toLowerCase(); + const isVerified = Boolean(user.isVerified); + + if (status === "rejected" || approvalStatus === "rejected") { + return "rejected"; + } + + if ( + status === "verified" || + status === "approved" || + approvalStatus === "verified" || + approvalStatus === "approved" || + isVerified + ) { + return "verified"; + } + + return "awaiting"; +} + +function getLocation(user) { + if (typeof user.location === "string" && user.location.trim()) { + return user.location; + } + + if (user.location && typeof user.location === "object") { + return ( + user.location.city || + user.location.state || + user.location.venue || + "—" + ); + } + + return user.city || user.state || "—"; +} + +function getDisplayName(user) { + return user.fullName || user.name || user.organizationName || "Unnamed Organizer"; +} + +function badgeText(status) { + if (status === "verified") return "Verified"; + if (status === "rejected") return "Rejected"; + return "Awaiting Verification"; +} + +function updatePendingCount() { + const count = organizers.filter( + (organizer) => organizer._verificationStatus === "awaiting" + ).length; + + elements.pendingCount.textContent = count; +} + +function renderTable() { + const query = elements.searchInput.value.trim().toLowerCase(); + + const filtered = organizers.filter((organizer) => { + const matchesFilter = + currentFilter === "all" + ? true + : organizer._verificationStatus === currentFilter; + + const searchableText = ` + ${getDisplayName(organizer)} + ${organizer.email || ""} + ${getLocation(organizer)} + ${organizer._verificationStatus} + `.toLowerCase(); + + return matchesFilter && searchableText.includes(query); + }); + + if (!filtered.length) { + elements.orgTable.innerHTML = ` + + No organizations found. + + `; + return; + } + + elements.orgTable.innerHTML = filtered + .map((organizer) => { + const id = organizer._id || organizer.id || ""; + const status = organizer._verificationStatus; + + return ` + + ${getDisplayName(organizer)} + ${organizer.email || "—"} + ${getLocation(organizer)} + ${badgeText(status)} + + + + + `; + }) + .join(""); + + attachViewDetailsHandlers(); +} + +function attachViewDetailsHandlers() { + document.querySelectorAll(".view").forEach((button) => { + button.addEventListener("click", () => { + const organizerId = button.dataset.id; + window.location.href = `verification-details.html?id=${encodeURIComponent(organizerId)}`; + }); + }); +} + +async function loadAdminProfile() { + try { + let profile; + + try { + profile = await apiRequest("/users/me"); + } catch { + profile = await apiRequest("/user/me"); + } + + elements.adminName.textContent = + profile.fullName || profile.name || "Admin"; + + if (profile.profileImage) { + elements.adminAvatar.src = profile.profileImage; + } + } catch (error) { + console.error("Failed to load admin profile:", error.message); + window.location.href = "../login/admin-login.html"; + } +} + +async function loadVerificationQueue() { + try { + let usersPayload; + + try { + usersPayload = await apiRequest("/user"); + } catch { + usersPayload = await apiRequest("/users"); + } + + const users = normalizeUsers(usersPayload); + + organizers = users + .filter((user) => String(user.role || "").toLowerCase() === "organizer") + .map((user) => ({ + ...user, + _verificationStatus: getVerificationStatus(user) + })); + + updatePendingCount(); + renderTable(); + } catch (error) { + console.error("Failed to load verification queue:", error.message); + elements.orgTable.innerHTML = ` + + Failed to load verification queue. + + `; + } +} + +function bindFilters() { + elements.filterButtons.forEach((button) => { + button.addEventListener("click", () => { + elements.filterButtons.forEach((btn) => btn.classList.remove("active")); + button.classList.add("active"); + currentFilter = button.dataset.filter; + renderTable(); + }); + }); +} + +function bindSearch() { + elements.searchInput.addEventListener("input", renderTable); +} + +document.addEventListener("DOMContentLoaded", async () => { + bindFilters(); + bindSearch(); + await loadAdminProfile(); + await loadVerificationQueue(); +}); + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireAdmin } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { getLocationText } from "../../assets/js/utils.js"; + +// const table = document.getElementById("orgTable"); +// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); + +// function statusOf(user) { +// const status = String(user.status || "").toLowerCase(); +// const approvalStatus = String(user.approvalStatus || "").toLowerCase(); +// if (status === "rejected" || approvalStatus === "rejected") return "rejected"; +// if (status === "verified" || approvalStatus === "approved" || user.isVerified) return "verified"; +// return "awaiting"; +// } + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireAdmin(); +// try { +// const payload = await apiRequest("/user"); +// const users = normalizeArray(payload, ["users"]); +// const organizers = users.filter((u) => String(u.role || "").toLowerCase() === "organizer"); +// table.innerHTML = organizers.map((user) => ` +// +// ${user.fullName || user.name || user.organizationName || "Unnamed Organizer"} +// ${user.email || "—"} +// ${getLocationText(user)} +// ${statusOf(user)} +// View Details +// +// `).join("") || `No organizations found.`; +// } catch { +// table.innerHTML = `Failed to load verification queue.`; +// } +// }); diff --git a/HowItWorks.html b/HowItWorks.html index 10c6678..b288f70 100644 --- a/HowItWorks.html +++ b/HowItWorks.html @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Images/WhatsApp Image 2026-03-08 at 15.32.16.jpeg b/Images/WhatsApp Image 2026-03-08 at 15.32.16.jpeg deleted file mode 100644 index f2515153e242851b6618a7ecea033c3885d86978..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41838 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 z 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 diff --git a/LandingPageStyle.css b/LandingPageStyle.css index db1f5ef..d45dfad 100644 --- a/LandingPageStyle.css +++ b/LandingPageStyle.css @@ -1,4 +1,4 @@ - +/* *{ margin:0; padding:0; @@ -60,10 +60,10 @@ body{ transform: translateY(-3px); } */ -.nav-buttons{ +/* .nav-buttons{ display:flex; gap:15px; -} +} */ /* .login{ background:none; @@ -81,7 +81,7 @@ body{ cursor:pointer; } */ - +/* .hero{ display:flex; @@ -140,7 +140,7 @@ body{ .primary-btn:hover { background-color: #e6c200; transform: translateY(-3px); -} +} */ /* .secondary-btn{ background:#243a5e; @@ -151,19 +151,19 @@ body{ cursor:pointer; } */ -.hero-image img{ + /* .hero-image img{ width:550px; border-top-left-radius: 50px; border-bottom-right-radius: 50px; - /* border-radius:20px; */ + border-radius:20px; box-shadow:0 10px 30px rgba(0,0,0,0.1); } - - + */ +/* .how-it-works { padding: 5rem 2rem; - /* background: #f9f9f9; - color: #333; */ + background: #f9f9f9; + color: #333; } .how-it-works-title { @@ -173,7 +173,7 @@ body{ .how-it-works-title h2 { font-size: 3.2rem; font-weight: 500; -} +} */ /* .how-it-works-card-container { display: flex; @@ -182,13 +182,13 @@ body{ } */ -.how-it-works-card-container { +/* .how-it-works-card-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 30px; - /* max-width: 1000px; */ + max-width: 1000px; margin: 0 auto; -} +} */ /* @media (max-width: 640px) { @@ -198,7 +198,7 @@ body{ } */ - +/* .how-it-works-card { background: #fff; border-radius: 10px; @@ -235,7 +235,7 @@ body{ justify-content: center; align-items: center; } - + */ @@ -244,16 +244,16 @@ body{ /* } */ - +/* .Benefits-title h2 { font-size: 4.2rem; - /* font-weight: 500; */ + font-weight: 500; -} - +} */ +/* .Benefits-card-container { display: grid; - gap:30px; + gap:30px; } @@ -272,32 +272,32 @@ body{ } } */ -.Benefits-card { +/* .Benefits-card { justify-content: space-between; - /* border: 3px; - border-color: #1FDD19; */ - /* padding: 20px; */ -} + border: 3px; + border-color: #1FDD19; + padding: 20px; +} */ -.Benefits-card h5 { +/* .Benefits-card h5 { margin-top: -110px; text-align: left; - /* max-width: 75%; */ - /* font-weight: 400; */ + max-width: 75%; + font-weight: 400; font-size: 2.5rem; color: #1f3a5f; -} +} */ -.Benefits-card p { +/* .Benefits-card p { text-align: left; - /* max-width: 75%; */ - /* padding: 10px; */ - /* margin-left: -20px; */ + max-width: 75%; + padding: 10px; + /margin-left: -20px; color: #1f3a5f; -} +} */ -.card-icon { +/* .card-icon { display: flex; background-color: #fff; @@ -309,11 +309,9 @@ body{ border-radius: 90px; color: #057DF2; margin-left:55rem; - /* float: right; */ - - -} - + float: right; +} */ +/* .line{ border:3px; height: 2px; @@ -440,4 +438,4 @@ body{ .copyright{ font-size:10px; color:#cbd5e1; -} \ No newline at end of file +} */ \ No newline at end of file diff --git a/Organizer/certificates/organizer-certificates.css b/Organizer/certificates/organizer-certificates.css new file mode 100644 index 0000000..7fa788c --- /dev/null +++ b/Organizer/certificates/organizer-certificates.css @@ -0,0 +1,366 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Poppins", sans-serif; +} + +body { + background: #f4f7fb; + color: #1f2f46; +} + +.dashboard-layout { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 15rem; + background: #1f3b63; + color: #ffffff; + padding: 1.5rem 1rem; + flex-shrink: 0; +} + +.sidebar-logo { + text-align: center; + margin-bottom: 2rem; +} + +.sidebar-logo img { + width: 5.5rem; + max-width: 100%; +} + +.sidebar-menu { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.55rem; +} + +.sidebar-menu li a, +.sidebar-logout { + width: 100%; + display: flex; + align-items: center; + gap: 0.85rem; + color: #ffffff; + text-decoration: none; + padding: 0.95rem 1rem; + border-radius: 0.65rem; + font-size: 0.96rem; + border: none; + background: transparent; + cursor: pointer; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover, +.sidebar-logout:hover { + background: #1f80ea; +} + +.main-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.topbar { + background: #ffffff; + padding: 2rem 2.2rem 1.2rem; + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.page-title { + font-size: 2rem; + font-weight: 700; + color: #223f6b; +} + +.page-subtitle { + margin-top: 0.3rem; + font-size: 0.9rem; + color: #6b7280; +} + +.topbar-right { + display: flex; + align-items: center; + gap: 0.9rem; +} + +.icon-btn { + width: 2.7rem; + height: 2.7rem; + border: none; + border-radius: 50%; + background: transparent; + color: #223f6b; + font-size: 1.35rem; + cursor: pointer; +} + +.page-content { + padding: 2.2rem; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1.4rem; + max-width: 40rem; + margin-bottom: 2rem; +} + +.stat-card { + background: #ffffff; + border-radius: 0.9rem; + padding: 1.2rem; + display: flex; + align-items: center; + gap: 1rem; + box-shadow: 0 0.15rem 0.45rem rgba(0, 0, 0, 0.05); +} + +.stat-icon { + width: 3rem; + height: 3rem; + border-radius: 0.7rem; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.15rem; + flex-shrink: 0; +} + +.stat-icon.gold { + background: #fff0d8; + color: #d29a1f; +} + +.stat-icon.green { + background: #dff2e4; + color: #2e8b57; +} + +.stat-icon.pink { + background: #f8d9dd; + color: #a23c4b; +} + +.stat-text p { + font-size: 0.72rem; + color: #8a94a6; + font-weight: 600; +} + +.stat-text h3 { + font-size: 1.9rem; + color: #223f6b; + font-weight: 700; + margin-top: 0.1rem; +} + +.table-wrapper { + background: #ffffff; + border-radius: 0.9rem; + overflow: hidden; + border: 0.08rem solid #d8dfe8; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #eef2f6; +} + +th, +td { + padding: 1rem; + text-align: left; + font-size: 0.92rem; +} + +th { + color: #35527a; + font-weight: 600; +} + +tbody tr { + border-bottom: 0.08rem solid #d7dde6; +} + +tbody tr:last-child { + border-bottom: none; +} + +.person-cell { + display: flex; + align-items: center; + gap: 0.8rem; +} + +.avatar { + width: 2.2rem; + height: 2.2rem; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; +} + +.status-badge { + display: inline-block; + min-width: 6.8rem; + text-align: center; + padding: 0.58rem 0.95rem; + border-radius: 0.42rem; + color: #ffffff; + font-size: 0.84rem; + font-weight: 600; +} + +.status-issued { + background: #2e8b57; +} + +.status-pending { + background: #f5ae42; +} + +.row-actions { + text-align: center; +} + +.row-actions button, +.row-actions a { + border: none; + background: transparent; + color: #223f6b; + font-size: 1.1rem; + cursor: pointer; + text-decoration: none; +} + +.table-footer { + margin-top: 1rem; + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + flex-wrap: wrap; + color: #4b5563; + font-size: 0.9rem; +} + +.pagination { + display: flex; + align-items: center; + gap: 0.35rem; + flex-wrap: wrap; +} + +.page-btn { + border: 0.08rem solid #d6dde6; + background: #ffffff; + color: #1f2f46; + padding: 0.42rem 0.75rem; + border-radius: 0.3rem; + cursor: pointer; + font-size: 0.8rem; +} + +.page-btn.active { + background: #1f80ea; + color: #ffffff; + border-color: #1f80ea; +} + +@media (max-width: 68rem) { + .stats-grid { + grid-template-columns: 1fr; + max-width: 100%; + } +} + +@media (max-width: 64rem) { + .dashboard-layout { + flex-direction: column; + } + + .sidebar { + width: 100%; + } + + .topbar { + flex-direction: column; + gap: 1rem; + } + + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 46rem; + } +} + + + + + + + + + +/* +.stats-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0,1fr)); + gap: 1rem; + max-width: 40rem; + margin-bottom: 2rem; +} + +.stat-card { + background: #fff; + border-radius: 0.9rem; + padding: 1rem; + border: 1px solid #d8dfe8; +} + +.stat-text p { + font-size: 0.75rem; + color: #8a94a6; +} + +.stat-text h3 { + font-size: 1.8rem; + color: #223f6b; +} + +.person-cell { + display: flex; + align-items: center; + gap: 0.8rem; +} + +.avatar { + width: 2.2rem; + height: 2.2rem; + border-radius: 50%; + object-fit: cover; +} + +@media (max-width: 68rem) { .stats-grid { + grid-template-columns: 1fr; +} +} */ diff --git a/Organizer/certificates/organizer-certificates.html b/Organizer/certificates/organizer-certificates.html new file mode 100644 index 0000000..394868a --- /dev/null +++ b/Organizer/certificates/organizer-certificates.html @@ -0,0 +1,185 @@ + + + + + + AIDLoop Organizer Certificates + + + + + + + +
    + + +
    +
    +
    +

    Certificates

    +

    Manage volunteer certificates

    +
    + +
    + + + +
    +
    + +
    +
    +
    +
    + +
    +
    +

    TOTAL CERTIFICATES

    +

    0

    +
    +
    + +
    +
    + +
    +
    +

    ISSUED

    +

    0

    +
    +
    + +
    +
    + +
    +
    +

    PENDING

    +

    0

    +
    +
    +
    + +
    + + + + + + + + + + + + + + + + +
    NameEVENTDateStatus
    Loading certificates...
    +
    + + +
    +
    +
    + + + + + + + + + + + + + + diff --git a/Organizer/certificates/organizer-certificates.js b/Organizer/certificates/organizer-certificates.js new file mode 100644 index 0000000..49945b3 --- /dev/null +++ b/Organizer/certificates/organizer-certificates.js @@ -0,0 +1,356 @@ +import { apiRequest, normalizeArray } from "../assets/js/api.js"; +import { requireRole } from "../assets/js/auth.js"; +import { logout } from "../assets/js/logout.js"; +import { ROUTES } from "../assets/js/config.js"; +import { formatDate } from "../assets/js/utils.js"; + +const els = { + totalCertificates: document.getElementById("totalCertificates"), + issuedCertificates: document.getElementById("issuedCertificates"), + pendingCertificates: document.getElementById("pendingCertificates"), + certificatesTable: document.getElementById("certificatesTable"), + tableCountText: document.getElementById("tableCountText"), + logoutBtn: document.getElementById("logoutBtn") +}; + +let organizer = null; +let certificateRows = []; + +function getAvatar(user) { + return user?.profileImage || "https://i.pravatar.cc/100?img=12"; +} + +function getCertificateId(item) { + return item._id || item.id || item.certificateId || ""; +} + +function getCertificateUserId(item) { + return ( + item.user?._id || + item.user?.id || + item.volunteer?._id || + item.volunteer?.id || + item.userId || + "" + ); +} + +function getCertificateEventId(item) { + return ( + item.event?._id || + item.event?.id || + item.eventId || + "" + ); +} + +function getCertificateUserName(item) { + return ( + item.user?.fullName || + item.user?.name || + item.volunteer?.fullName || + item.volunteer?.name || + item.volunteerName || + "Unknown Volunteer" + ); +} + +function getCertificateUserEmail(item) { + return ( + item.user?.email || + item.volunteer?.email || + item.email || + "—" + ); +} + +function getCertificateEventName(item) { + return ( + item.event?.name || + item.event?.title || + item.eventName || + "Untitled Event" + ); +} + +function rowKey(userId, eventId) { + return `${String(userId)}::${String(eventId)}`; +} + +function normalizeIssuedCertificates(records) { + return records.map((item) => ({ + type: "certificate", + status: "issued", + certificateId: getCertificateId(item), + userId: getCertificateUserId(item), + eventId: getCertificateEventId(item), + userName: getCertificateUserName(item), + userEmail: getCertificateUserEmail(item), + userAvatar: getAvatar(item.user || item.volunteer), + eventName: getCertificateEventName(item), + date: item.issuedAt || item.createdAt || item.event?.date || "", + raw: item + })); +} + +async function getOrganizerEvents() { + const eventsPayload = await apiRequest("/events"); + const allEvents = normalizeArray(eventsPayload, ["events"]); + const organizerId = String(organizer._id || organizer.id || ""); + + return allEvents.filter((event) => { + if (typeof event.organizer === "object" && event.organizer) { + return String(event.organizer._id || event.organizer.id || "") === organizerId; + } + return String(event.organizerId || "") === organizerId; + }); +} + +async function getRegistrationsForEvents(events) { + const allRegistrations = []; + + for (const event of events) { + const eventId = event._id || event.id; + const regsPayload = await apiRequest(`/applications/events/${eventId}/registrations`); + const registrations = Array.isArray(regsPayload) + ? regsPayload + : regsPayload.data || []; + + registrations.forEach((reg) => { + allRegistrations.push({ + ...reg, + _eventId: eventId, + _eventName: event.name || event.title || "Untitled Event", + _eventDate: event.date || reg.createdAt || "" + }); + }); + } + + return allRegistrations; +} + +function buildPendingRows(registrations, issuedKeys) { + return registrations + .filter((reg) => { + const userId = reg.user?._id || reg.user?.id || ""; + const eventId = reg._eventId || ""; + return !issuedKeys.has(rowKey(userId, eventId)); + }) + .map((reg) => ({ + type: "registration", + status: "pending", + certificateId: "", + userId: reg.user?._id || reg.user?.id || "", + eventId: reg._eventId || "", + userName: reg.user?.fullName || reg.user?.name || "Unknown Volunteer", + userEmail: reg.user?.email || "—", + userAvatar: getAvatar(reg.user), + eventName: reg._eventName, + date: reg._eventDate, + attendance: String(reg.attendance || "").toLowerCase(), + raw: reg + })); +} + +function renderStats(rows) { + const total = rows.length; + const issued = rows.filter((row) => row.status === "issued").length; + const pending = rows.filter((row) => row.status === "pending").length; + + els.totalCertificates.textContent = total; + els.issuedCertificates.textContent = issued; + els.pendingCertificates.textContent = pending; +} + +function renderTable(rows) { + els.tableCountText.textContent = `Showing ${rows.length} of ${rows.length} certificates`; + + if (!rows.length) { + els.certificatesTable.innerHTML = ` + + No certificate records found. + + `; + return; + } + + els.certificatesTable.innerHTML = rows.map((row) => ` + + +
    + ${row.userName} + ${row.userName} +
    + + ${row.eventName} + ${formatDate(row.date, "long")} + + + ${row.status === "issued" ? "Issued" : "Pending"} + + + + ${ + row.status === "issued" && row.certificateId + ? ` + + ` + : `` + } + + + `).join(""); +} + +async function loadCertificatesPage() { + const ownEvents = await getOrganizerEvents(); + const registrations = await getRegistrationsForEvents(ownEvents); + + let issuedPayload = []; + try { + issuedPayload = await apiRequest("/certificates/my-certificates"); + } catch { + issuedPayload = []; + } + + const issuedCertificates = normalizeArray(issuedPayload, ["certificates"]); + const issuedRows = normalizeIssuedCertificates(issuedCertificates); + + const issuedKeys = new Set( + issuedRows.map((row) => rowKey(row.userId, row.eventId)) + ); + + const pendingRows = buildPendingRows(registrations, issuedKeys); + + certificateRows = [...issuedRows, ...pendingRows].sort( + (a, b) => new Date(b.date || 0) - new Date(a.date || 0) + ); + + renderStats(certificateRows); + renderTable(certificateRows); +} + +els.logoutBtn.addEventListener("click", () => { + logout(ROUTES.organizerLogin); +}); + +document.addEventListener("DOMContentLoaded", async () => { + organizer = await requireRole("organizer", ROUTES.organizerLogin); + if (!organizer) return; + + try { + await loadCertificatesPage(); + } catch (error) { + els.certificatesTable.innerHTML = ` + + Failed to load certificates. + + `; + } +}); + + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireOrganizer } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { formatDate } from "../../assets/js/utils.js"; + +// const els = { +// totalCertificates: document.getElementById("totalCertificates"), +// issuedCertificates: document.getElementById("issuedCertificates"), +// pendingCertificates: document.getElementById("pendingCertificates"), +// certificatesTable: document.getElementById("certificatesTable"), +// tableCountText: document.getElementById("tableCountText"), +// logoutBtn: document.getElementById("logoutBtn") +// }; + +// function rowKey(userId, eventId) { return `${String(userId)}::${String(eventId)}`; } +// function avatar(user) { return user?.profileImage || "https://i.pravatar.cc/100?img=12"; } + +// function normalizeIssued(records) { +// return records.map((item) => ({ +// status: "issued", +// certificateId: item._id || item.id || item.certificateId || "", +// userId: item.user?._id || item.user?.id || item.volunteer?._id || item.volunteer?.id || item.userId || "", +// eventId: item.event?._id || item.event?.id || item.eventId || "", +// userName: item.user?.fullName || item.user?.name || item.volunteer?.fullName || item.volunteer?.name || item.volunteerName || "Unknown Volunteer", +// userAvatar: avatar(item.user || item.volunteer), +// eventName: item.event?.name || item.eventName || "Untitled Event", +// date: item.issuedAt || item.createdAt || item.event?.date || "" +// })); +// } + +// function render(rows) { +// els.totalCertificates.textContent = rows.length; +// els.issuedCertificates.textContent = rows.filter((r) => r.status === "issued").length; +// els.pendingCertificates.textContent = rows.filter((r) => r.status === "pending").length; +// els.tableCountText.textContent = `Showing ${rows.length} of ${rows.length} certificates`; +// els.certificatesTable.innerHTML = rows.map((row) => ` +// +//
    ${row.userName}${row.userName}
    +// ${row.eventName} +// ${formatDate(row.date, "long")} +// ${row.status === "issued" ? "Issued" : "Pending"} +// ${row.status === "issued" && row.certificateId ? `View` : "..."} +// +// `).join("") || `No certificate records found.`; +// } + +// document.addEventListener("DOMContentLoaded", async () => { +// const organizer = await requireOrganizer(); +// if (!organizer) return; +// els.logoutBtn.addEventListener("click", () => logout(ROUTES.home)); + +// try { +// const eventsPayload = await apiRequest("/events"); +// const allEvents = normalizeArray(eventsPayload, ["events"]); +// const organizerId = String(organizer._id || organizer.id || ""); +// const ownEvents = allEvents.filter((event) => { +// if (typeof event.organizer === "object" && event.organizer) { +// return String(event.organizer._id || event.organizer.id || "") === organizerId; +// } +// return String(event.organizerId || "") === organizerId; +// }); + +// const registrations = []; +// for (const event of ownEvents) { +// const data = await apiRequest(`/applications/events/${event._id || event.id}/registrations`); +// const regs = Array.isArray(data) ? data : data.data || []; +// regs.forEach((reg) => registrations.push({ ...reg, _eventId: event._id || event.id, _eventName: event.name || "Untitled Event", _eventDate: event.date })); +// } + +// let issuedPayload = []; +// try { issuedPayload = await apiRequest("/certificates/my-certificates"); } catch { issuedPayload = []; } +// const issuedRows = normalizeIssued(normalizeArray(issuedPayload, ["certificates"])); +// const issuedKeys = new Set(issuedRows.map((r) => rowKey(r.userId, r.eventId))); + +// const pendingRows = registrations +// .filter((reg) => !issuedKeys.has(rowKey(reg.user?._id || reg.user?.id || "", reg._eventId || ""))) +// .map((reg) => ({ +// status: "pending", +// certificateId: "", +// userId: reg.user?._id || reg.user?.id || "", +// eventId: reg._eventId, +// userName: reg.user?.fullName || reg.user?.name || "Unknown Volunteer", +// userAvatar: avatar(reg.user), +// eventName: reg._eventName, +// date: reg._eventDate || reg.createdAt || "" +// })); + +// render([...issuedRows, ...pendingRows].sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0))); +// } catch { +// els.certificatesTable.innerHTML = `Failed to load certificates.`; +// } +// }); diff --git a/Organizer/dashboard/organizer-dashboard.css b/Organizer/dashboard/organizer-dashboard.css new file mode 100644 index 0000000..b1f9d0f --- /dev/null +++ b/Organizer/dashboard/organizer-dashboard.css @@ -0,0 +1,321 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Poppins", sans-serif; +} + +body { + background: #f4f7fb; + color: #1f2f46; +} + +.dashboard-layout { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 15.5rem; + background: #1f3b63; + color: #fff; + padding: 1.5rem 1rem; + flex-shrink: 0; +} + +.sidebar-logo { + text-align: center; + margin-bottom: 2rem; +} + +.sidebar-logo img { + width: 6rem; +} + +.sidebar-menu { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.55rem; +} + +.sidebar-menu li a, +.sidebar-logout { + width: 100%; + display: flex; + align-items: center; + gap: 0.85rem; + color: #fff; + text-decoration: none; + padding: 0.95rem 1rem; + border-radius: 0.65rem; + font-size: 0.98rem; + border: none; + background: transparent; + cursor: pointer; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover, +.sidebar-logout:hover { + background: #1f80ea; +} + +.main-content { + flex: 1; + padding: 0; +} + +.topbar { + background: #fff; + padding: 2rem 2rem 1.2rem; + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.welcome-title { + font-size: 2rem; + font-weight: 700; + color: #223f6b; +} + +.welcome-subtitle { + color: #b7bec8; + font-size: 0.9rem; + margin-top: 0.3rem; +} + +.topbar-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +.create-event-btn { + background: #223f6b; + color: #fff; + text-decoration: none; + padding: 1rem 1.5rem; + border-radius: 0.8rem; + font-weight: 600; +} + +.icon-btn { + width: 2.8rem; + height: 2.8rem; + border: none; + border-radius: 50%; + background: transparent; + color: #223f6b; + font-size: 1.5rem; + cursor: pointer; +} + +.profile-mini img { + width: 2.6rem; + height: 2.6rem; + border-radius: 50%; + object-fit: cover; +} + +.dashboard-section, +.recent-events-section { + padding: 2rem; +} + +.dashboard-section h2, +.recent-events-section h2 { + font-size: 1.9rem; + color: #223f6b; + margin-bottom: 1.5rem; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 1.5rem; +} + +.stat-card { + background: #fff; + border: 0.08rem solid #9ca9bb; + border-radius: 0.9rem; + overflow: hidden; +} + +.stat-number { + text-align: center; + font-size: 2.2rem; + font-weight: 700; + padding: 1rem 0; + color: #111; +} + +.stat-footer { + border-top: 0.08rem solid #9ca9bb; + display: flex; + align-items: center; + gap: 0.6rem; + padding: 0.95rem 1rem; + color: #526176; + font-size: 0.82rem; +} + +.stat-footer i { + color: #1f80ea; + font-size: 1.15rem; +} + +.section-divider { + height: 0.08rem; + background: #aeb8c6; + margin-bottom: 2rem; +} + +.table-wrapper { + background: #fff; + border-radius: 0.9rem; + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #eef2f6; +} + +th, td { + padding: 1.2rem 1.1rem; + text-align: left; + font-size: 0.95rem; +} + +th { + color: #35527a; + font-weight: 600; +} + +tbody tr { + border-bottom: 0.08rem solid #d7dde6; +} + +tbody tr:last-child { + border-bottom: none; +} + +.status-badge { + display: inline-block; + min-width: 7rem; + text-align: center; + padding: 0.65rem 1rem; + border-radius: 0.45rem; + color: #fff; + font-size: 0.9rem; + font-weight: 600; +} + +.status-upcoming { + background: #f5ae42; +} + +.status-published { + background: #26a65b; +} + +.status-draft { + background: #6f809b; +} + +.status-completed { + background: #1f80ea; +} + +.row-actions { + color: #223f6b; + font-size: 1.3rem; + text-align: center; +} + +@media (max-width: 68rem) { + .stats-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 52rem) { + .dashboard-layout { + flex-direction: column; + } + + .sidebar { + width: 100%; + } + + .topbar { + flex-direction: column; + gap: 1rem; + } + + .stats-grid { + grid-template-columns: 1fr; + } + + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 52rem; + } +} + + + + + + + + + +/* .stats-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0,1fr)); + gap: 1rem; + margin-bottom: 2rem; +} + +.stat-card { + background: #fff; + border: 1px solid #d8dfe8; + border-radius: 0.9rem; + overflow: hidden; +} + +.stat-number { + text-align: center; + font-size: 2rem; + font-weight: 700; + padding: 1rem 0; +} + +.stat-footer { + padding: 0.85rem 1rem; + border-top: 1px solid #d8dfe8; + color: #526176; +} + +@media (max-width: 68rem) { + .stats-grid { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 48rem) { + .stats-grid { + grid-template-columns: 1fr; + } +} */ diff --git a/Organizer/dashboard/organizer-dashboard.html b/Organizer/dashboard/organizer-dashboard.html new file mode 100644 index 0000000..232787f --- /dev/null +++ b/Organizer/dashboard/organizer-dashboard.html @@ -0,0 +1,159 @@ + + + + + + AIDLoop Organizer Dashboard + + + + + +
    + + +
    +
    +
    +

    Welcome Back

    +

    An overview of your events and volunteer activity

    +
    + +
    + Create Event + +
    + Organizer Avatar +
    +
    +
    + +
    +

    Dashboard

    + +
    +
    +
    0
    + +
    + +
    +
    0
    + +
    + +
    +
    0
    + +
    + +
    +
    0
    + +
    +
    +
    + +
    +
    +

    Recent Events

    + +
    + + + + + + + + + + + + + + + + +
    Events NameLocationDateVolunteersStatus
    Loading events...
    +
    +
    +
    +
    + + + + + + + + + + + + + diff --git a/Organizer/dashboard/organizer-dashboard.js b/Organizer/dashboard/organizer-dashboard.js new file mode 100644 index 0000000..72d959e --- /dev/null +++ b/Organizer/dashboard/organizer-dashboard.js @@ -0,0 +1,247 @@ +import { apiRequest, normalizeArray } from "../assets/js/api.js"; +import { requireRole } from "../assets/js/auth.js"; +import { formatDate } from "../assets/js/utils.js"; +import { ROUTES } from "../assets/js/config.js"; +import { logout } from "../assets/js/logout.js"; + +const els = { + organizerAvatar: document.getElementById("organizerAvatar"), + totalEvents: document.getElementById("totalEvents"), + upcomingEvents: document.getElementById("upcomingEvents"), + completedEvents: document.getElementById("completedEvents"), + totalVolunteers: document.getElementById("totalVolunteers"), + eventsTable: document.getElementById("eventsTable"), + logoutBtn: document.getElementById("logoutBtn") +}; + +let organizer = null; +let allEvents = []; + +function getLocation(event) { + if (typeof event.location === "string" && event.location.trim()) { + return event.location; + } + + if (event.location && typeof event.location === "object") { + const venue = event.location.venue || ""; + const city = event.location.city || ""; + const state = event.location.state || ""; + return [venue, city || state].filter(Boolean).join(", ") || "—"; + } + + return event.city || "—"; +} + +function getEventStatus(event) { + const raw = String(event.status || "").toLowerCase(); + + if (raw === "cancelled" || raw === "canceled") return "draft"; + if (raw === "draft") return "draft"; + if (raw === "published") return "published"; + + const eventDate = event.date ? new Date(event.date) : null; + const now = new Date(); + + if (eventDate && !Number.isNaN(eventDate.getTime()) && eventDate < now) { + return "completed"; + } + + return "upcoming"; +} + +function getStatusText(status) { + if (status === "published") return "Published"; + if (status === "draft") return "Draft"; + if (status === "completed") return "Completed"; + return "Upcoming"; +} + +function getVolunteerCount(event) { + const filled = + event.filledSlots ?? + event.registrationsCount ?? + event.registeredCount ?? + event.attendeesCount ?? + 0; + + const total = + event.volunteerSlots ?? + event.totalSlots ?? + event.capacity ?? + 0; + + return `${filled}/${total}`; +} + +function computeStats(events) { + const totalEvents = events.length; + const upcomingEvents = events.filter((event) => { + const status = getEventStatus(event); + return status === "upcoming" || status === "published"; + }).length; + + const completedEvents = events.filter((event) => getEventStatus(event) === "completed").length; + + const totalVolunteers = events.reduce((sum, event) => { + return sum + ( + event.filledSlots ?? + event.registrationsCount ?? + event.registeredCount ?? + event.attendeesCount ?? + 0 + ); + }, 0); + + els.totalEvents.textContent = totalEvents; + els.upcomingEvents.textContent = upcomingEvents; + els.completedEvents.textContent = completedEvents; + els.totalVolunteers.textContent = totalVolunteers; +} + +function renderEvents(events) { + if (!events.length) { + els.eventsTable.innerHTML = ` + + No events found. + + `; + return; + } + + const recentEvents = [...events] + .sort((a, b) => new Date(b.createdAt || b.date || 0) - new Date(a.createdAt || a.date || 0)) + .slice(0, 5); + + els.eventsTable.innerHTML = recentEvents.map((event) => { + const status = getEventStatus(event); + const id = event._id || event.id || ""; + + return ` + + ${event.name || event.title || "Untitled Event"} + ${getLocation(event)} + ${formatDate(event.date, "long")} + ${getVolunteerCount(event)} + ${getStatusText(status)} + + + + + + + `; + }).join(""); +} + +async function loadOrganizerDashboard() { + const payload = await apiRequest("/events"); + const events = normalizeArray(payload, ["events"]); + + const organizerId = String(organizer._id || organizer.id || ""); + + allEvents = events.filter((event) => { + if (typeof event.organizer === "object" && event.organizer) { + return String(event.organizer._id || event.organizer.id || "") === organizerId; + } + + return String(event.organizerId || "") === organizerId; + }); + + computeStats(allEvents); + renderEvents(allEvents); +} + +document.addEventListener("DOMContentLoaded", async () => { + organizer = await requireRole("organizer", ROUTES.organizerLogin); + if (!organizer) return; + + if (organizer.profileImage) { + els.organizerAvatar.src = organizer.profileImage; + } + + els.logoutBtn.addEventListener("click", () => { + logout(ROUTES.organizerLogin); + }); + + try { + await loadOrganizerDashboard(); + } catch (error) { + els.eventsTable.innerHTML = ` + + Failed to load dashboard data. + + `; + } +}); + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireOrganizer } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { formatDate, getLocationText } from "../../assets/js/utils.js"; + +// const els = { +// totalEvents: document.getElementById("totalEvents"), +// upcomingEvents: document.getElementById("upcomingEvents"), +// completedEvents: document.getElementById("completedEvents"), +// totalVolunteers: document.getElementById("totalVolunteers"), +// eventsTable: document.getElementById("eventsTable"), +// logoutBtn: document.getElementById("logoutBtn") +// }; + +// let organizer; + +// function getStatus(event) { +// const raw = String(event.status || "").toLowerCase(); +// if (raw === "draft") return "draft"; +// if (raw === "cancelled" || raw === "canceled") return "cancelled"; +// const eventDate = event.date ? new Date(event.date) : null; +// if (raw === "published" && eventDate && eventDate < new Date()) return "completed"; +// if (raw === "published") return "published"; +// return "published"; +// } + +// document.addEventListener("DOMContentLoaded", async () => { +// organizer = await requireOrganizer(); +// if (!organizer) return; + +// els.logoutBtn.addEventListener("click", () => logout(ROUTES.home)); + +// try { +// const payload = await apiRequest("/events"); +// const allEvents = normalizeArray(payload, ["events"]); +// const organizerId = String(organizer._id || organizer.id || ""); +// const events = allEvents.filter((event) => { +// if (typeof event.organizer === "object" && event.organizer) { +// return String(event.organizer._id || event.organizer.id || "") === organizerId; +// } +// return String(event.organizerId || "") === organizerId; +// }); + +// const totalVolunteers = events.reduce((sum, event) => sum + (event.filledSlots ?? event.registrationsCount ?? 0), 0); +// els.totalEvents.textContent = events.length; +// els.upcomingEvents.textContent = events.filter((e) => getStatus(e) === "published").length; +// els.completedEvents.textContent = events.filter((e) => getStatus(e) === "completed").length; +// els.totalVolunteers.textContent = totalVolunteers; + +// els.eventsTable.innerHTML = events.slice(0, 5).map((event) => ` +// +// ${event.name || "Untitled Event"} +// ${getLocationText(event)} +// ${formatDate(event.date, "long")} +// ${event.filledSlots ?? event.registrationsCount ?? 0}/${event.volunteerSlots ?? 0} +// ${getStatus(event)} +// +// `).join("") || `No events found.`; +// } catch { +// els.eventsTable.innerHTML = `Failed to load dashboard data.`; +// } +// }); diff --git a/Organizer/email-verified/email-verified.css b/Organizer/email-verified/email-verified.css new file mode 100644 index 0000000..63e75eb --- /dev/null +++ b/Organizer/email-verified/email-verified.css @@ -0,0 +1,192 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Poppins", sans-serif; +} + +body { + background: #f5f7fa; + min-height: 100vh; +} + +.verified-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.verified-card { + width: 100%; + max-width: 40rem; + min-height: 46rem; + background: #ffffff; + padding: 4rem 2rem; + text-align: center; + box-shadow: 0 0.625rem 2.5rem rgba(0, 0, 0, 0.05); +} + +.icon-wrap { + display: flex; + justify-content: center; + margin-bottom: 1.5rem; +} + +.success-icon { + width: 7rem; + height: 7rem; + border-radius: 50%; + background: #79e36d; + border: 0.1875rem solid #1fc933; + display: flex; + align-items: center; + justify-content: center; + position: relative; + clip-path: polygon( + 50% 0%, 61% 8%, 75% 8%, 82% 20%, 95% 25%, 92% 39%, 100% 50%, + 92% 61%, 95% 75%, 82% 80%, 75% 92%, 61% 92%, 50% 100%, 39% 92%, + 25% 92%, 18% 80%, 5% 75%, 8% 61%, 0% 50%, 8% 39%, 5% 25%, 18% 20%, + 25% 8%, 39% 8% + ); +} + +.success-icon i { + font-size: 2.3rem; + color: #ffffff; +} + +.verified-card h1 { + font-size: 2rem; + color: #223f6b; + margin-bottom: 1.2rem; + font-weight: 700; +} + +.mini-text { + min-height: 1rem; + margin-bottom: 2rem; + font-size: 0.8rem; + color: #6b7280; +} + +.divider { + width: 100%; + height: 0.0625rem; + background: #cfd5dc; + margin: 2rem 0 1.5rem; +} + +.message { + max-width: 26rem; + margin: 0 auto 2rem; + font-size: 1.25rem; + line-height: 1.7; + color: #444444; +} + +.change-email-link { + display: inline-block; + margin-bottom: 1rem; + font-size: 1.15rem; + color: #0d6efd; + text-decoration: none; + font-weight: 500; +} + +.change-email-link:hover { + text-decoration: underline; +} + +.back-link { + display: inline-block; + margin: 0.5rem 0 3rem; + font-size: 1.2rem; + color: #8b8b8b; + text-decoration: none; +} + +.back-link:hover { + color: #223f6b; +} + +.primary-btn { + width: 100%; + max-width: 28rem; + border: none; + background: #223f6b; + color: #ffffff; + padding: 1rem 1.25rem; + border-radius: 0.75rem; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; +} + +.primary-btn:hover { + opacity: 0.95; +} + +.primary-btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +@media (max-width: 40rem) { + .verified-card { + min-height: auto; + padding: 3rem 1.25rem; + } + + .verified-card h1 { + font-size: 1.6rem; + } + + .message { + font-size: 1rem; + } + + .change-email-link, + .back-link { + font-size: 1rem; + } + + .success-icon { + width: 6rem; + height: 6rem; + } +} + + + + + + + + + +/* +.auth-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.auth-card { + width: 100%; + max-width: 36rem; + background: #fff; + border-radius: 1rem; + padding: 2rem; + text-align: center; + box-shadow: 0 0.5rem 2rem rgba(0,0,0,0.06); +} + +.auth-card h1 { + color: #223f6b; + margin-bottom: 1rem; +} + */ diff --git a/Organizer/email-verified/email-verified.html b/Organizer/email-verified/email-verified.html new file mode 100644 index 0000000..e61dad1 --- /dev/null +++ b/Organizer/email-verified/email-verified.html @@ -0,0 +1,67 @@ + + + + + + Email Verified Successfully + + + + + +
    +
    +
    +
    + +
    +
    + +

    Email Verified Successfully

    + +
    + +
    + +

    + Your email has been verified. You can now access your dashboard. +

    + + + Change Email Address + + +
    + + + Back to Login + + + +
    +
    + + + + + + + + + + diff --git a/Organizer/email-verified/email-verified.js b/Organizer/email-verified/email-verified.js new file mode 100644 index 0000000..a97ff02 --- /dev/null +++ b/Organizer/email-verified/email-verified.js @@ -0,0 +1,64 @@ +import { apiRequest } from "../assets/js/api.js"; +import { ROUTES } from "../assets/js/config.js"; + +const els = { + continueBtn: document.getElementById("continueBtn"), + statusText: document.getElementById("statusText") +}; + +function setLoading(isLoading) { + els.continueBtn.disabled = isLoading; + els.continueBtn.textContent = isLoading + ? "Checking session..." + : "Continue to Dashboard"; +} + +async function checkUserSession() { + try { + setLoading(true); + + let user; + try { + user = await apiRequest("/users/me"); + } catch { + user = await apiRequest("/user/me"); + } + + if (user && String(user.role || "").toLowerCase() === "organizer") { + els.statusText.textContent = "You are already signed in."; + return true; + } + + els.statusText.textContent = ""; + return false; + } catch { + els.statusText.textContent = ""; + return false; + } finally { + setLoading(false); + } +} + +els.continueBtn.addEventListener("click", async () => { + const hasSession = await checkUserSession(); + + if (hasSession) { + window.location.href = ROUTES.organizerDashboard; + return; + } + + window.location.href = ROUTES.organizerLogin; +}); + +document.addEventListener("DOMContentLoaded", async () => { + await checkUserSession(); +}); + + + + + +// import { ROUTES } from "../../assets/js/config.js"; +// document.getElementById("continueBtn").addEventListener("click", () => { +// window.location.href = ROUTES.organizerLogin; +// }); diff --git a/Organizer/events/cancel-event.css b/Organizer/events/cancel-event.css new file mode 100644 index 0000000..6871020 --- /dev/null +++ b/Organizer/events/cancel-event.css @@ -0,0 +1,128 @@ +.cancel-section { + padding: 2rem; + max-width: 700px; +} + +.warning { + text-align: center; + margin-bottom: 2rem; +} + +.warning i { + font-size: 3rem; + color: orange; +} + +.warning p { + margin-top: 1rem; + color: #333; +} + +.reasons h3 { + margin-bottom: 1rem; +} + +.checkbox-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.8rem; +} + +.reason-text { + margin-top: 2rem; +} + +textarea { + width: 100%; + height: 120px; + padding: 0.8rem; + border-radius: 8px; + border: 1px solid #ccc; +} + +.actions { + display: flex; + gap: 1rem; + margin-top: 2rem; +} + +.btn { + padding: 0.8rem 1.4rem; + border-radius: 6px; + border: none; + cursor: pointer; +} + +.btn.danger { + background: red; + color: #fff; +} + +.btn.secondary { + background: #2c4a74; + color: #fff; +} + +/* MODAL */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.4); + display: flex; + justify-content: center; + align-items: center; + z-index: 999; +} + +.hidden { + display: none; +} + +.modal-card { + background: #fff; + padding: 2rem; + border-radius: 16px; + width: 400px; + max-width: 90%; + text-align: center; + position: relative; +} + +.close-btn { + position: absolute; + top: 10px; + right: 14px; + font-size: 1.4rem; + border: none; + background: none; + cursor: pointer; +} + +.warning-icon { + font-size: 3rem; + color: orange; + margin-bottom: 1rem; +} + +.modal-content p { + margin-bottom: 2rem; + color: #333; +} + +.btn.full { + width: 100%; + margin-bottom: 1rem; +} + +.btn.outline { + background: transparent; + border: 2px solid #2c4a74; + color: #2c4a74; +} + + + +/* Uses shared layout/forms */ \ No newline at end of file diff --git a/Organizer/events/cancel-event.html b/Organizer/events/cancel-event.html new file mode 100644 index 0000000..950b3e2 --- /dev/null +++ b/Organizer/events/cancel-event.html @@ -0,0 +1,126 @@ + + + + + + Cancel Event + + + + + + + + +
    + + + + + +
    +
    +

    Cancel Event

    +
    + +
    + +
    + +

    + Cancelling this event will notify all registered volunteers and cannot be undone +

    +
    + + +
    +

    Reasons for cancellation

    + +
    + + + + + + +
    +
    + + +
    + + +
    + + +
    + + +
    + +
    +
    +
    + + + + + + + + + + + + + + + diff --git a/Organizer/events/cancel-event.js b/Organizer/events/cancel-event.js new file mode 100644 index 0000000..3bbbf67 --- /dev/null +++ b/Organizer/events/cancel-event.js @@ -0,0 +1,123 @@ +import { apiRequest } from "../assets/js/api.js"; +import { requireRole } from "../assets/js/auth.js"; +import { logout } from "../assets/js/logout.js"; +import { ROUTES } from "../assets/js/config.js"; + +const eventId = new URLSearchParams(window.location.search).get("id"); + +const els = { + cancelBtn: document.getElementById("cancelEventBtn"), + goBackBtn: document.getElementById("goBackBtn"), + reasonText: document.getElementById("reasonText"), + logoutBtn: document.getElementById("logoutBtn"), + confirmModal: document.getElementById("confirmModal"), + confirmCancel: document.getElementById("confirmCancel"), + closeModal: document.getElementById("closeModal"), + cancelModal: document.getElementById("cancelModal") +}; + +function getSelectedReasons() { + return [...document.querySelectorAll("input[type='checkbox']:checked")] + .map((cb) => cb.value); +} + +function openModal() { + els.confirmModal.classList.remove("hidden"); +} + +function hideModal() { + els.confirmModal.classList.add("hidden"); +} + +async function cancelEvent() { + const reasons = getSelectedReasons(); + const text = els.reasonText.value.trim(); + + if (!reasons.length && !text) { + alert("Please provide a reason"); + hideModal(); + return; + } + + const reason = [...reasons, text].filter(Boolean).join(", "); + + try { + els.confirmCancel.disabled = true; + els.confirmCancel.textContent = "Cancelling..."; + + await apiRequest(`/events/${eventId}/cancel`, { + method: "PATCH", + body: JSON.stringify({ reason }) + }); + + alert("Event cancelled successfully"); + window.location.href = ROUTES.eventListing; + } catch (err) { + alert(err.message || "Failed to cancel event"); + } finally { + els.confirmCancel.disabled = false; + els.confirmCancel.textContent = "Yes, Cancel event"; + } +} + +els.cancelBtn.addEventListener("click", openModal); + +els.goBackBtn.addEventListener("click", () => { + window.history.back(); +}); + +[els.closeModal, els.cancelModal].forEach((btn) => { + btn.addEventListener("click", hideModal); +}); + +els.confirmCancel.addEventListener("click", cancelEvent); + +els.logoutBtn.addEventListener("click", () => { + logout(ROUTES.organizerLogin); +}); + +document.addEventListener("DOMContentLoaded", async () => { + await requireRole("organizer", ROUTES.organizerLogin); + + if (!eventId) { + alert("Invalid event"); + window.location.href = ROUTES.eventListing; + return; + } +}); + + + + + + + + + +// import { apiRequest } from "../../assets/js/api.js"; +// import { requireOrganizer } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { getQueryParam } from "../../assets/js/utils.js"; + +// const eventId = getQueryParam("id"); +// const reasonText = document.getElementById("reasonText"); +// const cancelBtn = document.getElementById("cancelEventBtn"); +// const msg = document.getElementById("msg"); +// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); + +// cancelBtn.addEventListener("click", async () => { +// try { +// await apiRequest(`/events/${eventId}/cancel`, { +// method: "PATCH", +// body: JSON.stringify({ reason: reasonText.value.trim() || "Cancelled by organizer" }) +// }); +// msg.textContent = "Event cancelled successfully."; +// setTimeout(() => { window.location.href = ROUTES.organizerEventListing; }, 600); +// } catch (err) { +// msg.className = "form-error"; +// msg.textContent = err.message || "Failed to cancel event."; +// } +// }); + +// document.addEventListener("DOMContentLoaded", requireOrganizer); diff --git a/Organizer/events/create-event.css b/Organizer/events/create-event.css new file mode 100644 index 0000000..707d751 --- /dev/null +++ b/Organizer/events/create-event.css @@ -0,0 +1,82 @@ +.form-section { + padding: 2rem; + max-width: 600px; +} + +.image-upload { + border: 2px dashed #1f80ea; + padding: 2rem; + text-align: center; + margin-bottom: 1.5rem; + cursor: pointer; +} + +input, textarea { + width: 100%; + padding: 0.7rem; + margin: 0.5rem 0 1rem; + border-radius: 6px; + border: 1px solid #ccc; +} + +.grid-2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} + +.tags-input { + display: flex; + gap: 5px; +} + +#rolesList span { + background: #1f80ea; + color: white; + padding: 5px 10px; + margin: 5px; + display: inline-block; + border-radius: 5px; +} + +.actions { + display: flex; + justify-content: space-between; + margin-top: 20px; +} + +.actions button { + padding: 10px 20px; + border: none; + border-radius: 6px; + cursor: pointer; +} + +.actions button:first-child { + background: transparent; + border: 1px solid #1f80ea; +} + +.actions button:last-child { + background: #1f3b63; + color: white; +} + + + + + + + +/* +.create-form { + max-width: 42rem; + display: grid; + gap: 0.75rem; +} + +.actions { + display: flex; + gap: 1rem; + margin-top: 1rem; +} */ diff --git a/Organizer/events/create-event.html b/Organizer/events/create-event.html new file mode 100644 index 0000000..2f86778 --- /dev/null +++ b/Organizer/events/create-event.html @@ -0,0 +1,164 @@ + + + + + + Create Event + + + + + + + + +
    + + + + + MAIN +
    + +
    +

    Create Event

    +
    + +
    + + +
    + + +

    Upload Event Image

    +
    + + +
    + + + + + + + + +
    + + +
    +
    + + + + + + + +
    +
    + + +
    + +
    + + +
    +
    + +
    +
    + + +
    + +
    + + +
    +
    + + + + + + + + + +
    + + +
    + +

    + +
    + +
    + +
    +
    + + + + + + + + + + + + + + + diff --git a/Organizer/events/create-event.js b/Organizer/events/create-event.js new file mode 100644 index 0000000..eba14ff --- /dev/null +++ b/Organizer/events/create-event.js @@ -0,0 +1,162 @@ +import { apiRequest } from "../assets/js/api.js"; +import { ROUTES } from "../assets/js/config.js"; +import { logout } from "../assets/js/logout.js"; + +const roles = []; +let imageUrl = ""; + +const els = { + form: document.getElementById("eventForm"), + roleInput: document.getElementById("roleInput"), + addRole: document.getElementById("addRole"), + rolesList: document.getElementById("rolesList"), + imageInput: document.getElementById("imageInput"), + imageBox: document.getElementById("imageBox"), + formMsg: document.getElementById("formMsg"), + saveDraft: document.getElementById("saveDraft"), + logoutBtn: document.getElementById("logoutBtn") +}; + +/* IMAGE */ +els.imageBox.addEventListener("click", () => els.imageInput.click()); + +els.imageInput.addEventListener("change", async (e) => { + const file = e.target.files[0]; + if (!file) return; + + // TEMP: Replace with Cloudinary later + imageUrl = URL.createObjectURL(file); + els.imageBox.innerHTML = ``; +}); + +/* ROLES */ +els.addRole.addEventListener("click", () => { + const val = els.roleInput.value.trim(); + if (!val) return; + + roles.push(val); + els.roleInput.value = ""; + renderRoles(); +}); + +function renderRoles() { + els.rolesList.innerHTML = roles.map(r => `${r}`).join(""); +} + +/* CREATE EVENT */ +async function createEvent(status = "draft") { + const payload = { + name: document.getElementById("name").value, + category: document.getElementById("category").value, + description: document.getElementById("description").value, + location: { + venue: document.getElementById("venue").value, + city: document.getElementById("city").value + }, + image: imageUrl, + date: document.getElementById("date").value, + startTime: document.getElementById("startTime").value, + endTime: document.getElementById("endTime").value, + volunteerSlots: Number(document.getElementById("slots").value), + roles, + certificateEnabled: document.getElementById("certificateEnabled").checked, + requirements: document + .getElementById("requirements") + .value.split("\n") + }; + + try { + const res = await apiRequest("/events", { + method: "POST", + body: JSON.stringify(payload) + }); + + const eventId = res._id || res.id; + + if (status === "published") { + await apiRequest(`/events/${eventId}/status`, { + method: "PATCH", + body: JSON.stringify({ status: "published" }) + }); + } + + els.formMsg.textContent = "Event created successfully!"; + window.location.href = ROUTES.organizerDashboard; + + } catch (err) { + els.formMsg.textContent = err.message; + } +} + +/* ACTIONS */ +els.form.addEventListener("submit", (e) => { + e.preventDefault(); + createEvent("published"); +}); + +els.saveDraft.addEventListener("click", () => createEvent("draft")); + +/* LOGOUT */ +els.logoutBtn.addEventListener("click", () => { + logout(ROUTES.organizerLogin); +}); + + + + + + + + + + +// import { apiRequest } from "../../assets/js/api.js"; +// import { requireOrganizer } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; + +// const form = document.getElementById("eventForm"); +// const formError = document.getElementById("formError"); +// const formSuccess = document.getElementById("formSuccess"); +// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); +// document.getElementById("saveDraft").addEventListener("click", () => submitEvent("draft")); +// form.addEventListener("submit", (e) => { e.preventDefault(); submitEvent("published"); }); + +// async function submitEvent(status) { +// formError.textContent = ""; +// formSuccess.textContent = ""; +// try { +// const result = await apiRequest("/events", { +// method: "POST", +// body: JSON.stringify({ +// name: document.getElementById("name").value, +// category: document.getElementById("category").value, +// description: document.getElementById("description").value, +// location: { venue: document.getElementById("venue").value, city: document.getElementById("city").value }, +// image: "", +// date: document.getElementById("date").value, +// startTime: document.getElementById("startTime").value, +// endTime: document.getElementById("endTime").value, +// volunteerSlots: Number(document.getElementById("slots").value), +// roles: document.getElementById("roles").value.split(",").map((v) => v.trim()).filter(Boolean), +// certificateEnabled: document.getElementById("certificateEnabled").checked, +// requirements: document.getElementById("requirements").value.split(",").map((v) => v.trim()).filter(Boolean) +// }) +// }); + +// const eventId = result._id || result.id; +// if (status === "published") { +// await apiRequest(`/events/${eventId}/status`, { +// method: "PATCH", +// body: JSON.stringify({ status: "published" }) +// }); +// } + +// formSuccess.textContent = "Event created successfully."; +// setTimeout(() => { window.location.href = ROUTES.organizerEventListing; }, 700); +// } catch (err) { +// formError.textContent = err.message || "Failed to create event."; +// } +// } + +// document.addEventListener("DOMContentLoaded", requireOrganizer); diff --git a/Organizer/events/event-details.css b/Organizer/events/event-details.css new file mode 100644 index 0000000..91d2af6 --- /dev/null +++ b/Organizer/events/event-details.css @@ -0,0 +1,88 @@ +.event-details { + padding: 2rem; +} + +.event-image { + position: relative; + margin: 1rem 0; +} + +.event-image img { + width: 100%; + border-radius: 10px; +} + +.status-badge { + position: absolute; + top: 15px; + right: 15px; + padding: 6px 14px; + border-radius: 6px; + color: #fff; + font-size: 0.85rem; +} + +.status-published { background: green; } +.status-draft { background: gray; } +.status-cancelled { background: red; } + +.description-box { + border: 1px solid #ccc; + padding: 1rem; + border-radius: 8px; + margin: 1rem 0; +} + +.event-meta { + display: flex; + gap: 2rem; + margin: 1rem 0; +} + +.requirements ul { + padding-left: 1.5rem; +} + +.stats { + display: flex; + gap: 2rem; + margin: 2rem 0; +} + +.stat { + background: #f3f5f9; + padding: 1rem; + border-radius: 8px; + text-align: center; +} + +.table-wrapper { + margin-top: 1.5rem; +} + +table { + width: 100%; + border-collapse: collapse; +} + +td, th { + padding: 0.8rem; + border-bottom: 1px solid #ddd; +} + + + + + + +/* +.meta { + display: flex; + gap: 1rem; + margin: 1rem 0 2rem; + color: #6b7280; +} + +#eventDescription { + margin-top: 1rem; +} */ diff --git a/Organizer/events/event-details.html b/Organizer/events/event-details.html new file mode 100644 index 0000000..0d826ec --- /dev/null +++ b/Organizer/events/event-details.html @@ -0,0 +1,136 @@ + + + + + + Event Details + + + + + + + + +
    + + + + + +
    + + +
    +

    Events

    + +
    + + +
    +
    + + +
    + +

    Loading...

    + +
    + Event Image + Status +
    + +

    Events Description

    +
    --> + + +
    + + + +
    + + +
    +

    Volunteer Requirements

    +
      +
      + + +
      +
      +

      0

      +

      Total Slots

      +
      +
      +

      0

      +

      Registered

      +
      +
      +

      0

      +

      Remaining

      +
      +
      + + +
      + + + + + + + + + + + + + +
      NameEmailDateStatus
      Loading...
      +
      + +
      +
      +
      + + + + + + + + + + + + + + + diff --git a/Organizer/events/event-details.js b/Organizer/events/event-details.js new file mode 100644 index 0000000..ffdccaf --- /dev/null +++ b/Organizer/events/event-details.js @@ -0,0 +1,161 @@ +import { apiRequest } from "../assets/js/api.js"; +import { requireRole } from "../assets/js/auth.js"; +import { logout } from "../assets/js/logout.js"; + +const els = { + name: document.getElementById("eventName"), + image: document.getElementById("eventImage"), + description: document.getElementById("eventDescription"), + time: document.getElementById("eventTime"), + date: document.getElementById("eventDate"), + location: document.getElementById("eventLocation"), + requirements: document.getElementById("requirementsList"), + totalSlots: document.getElementById("totalSlots"), + registered: document.getElementById("registered"), + remaining: document.getElementById("remaining"), + table: document.getElementById("volunteerTable"), + statusBadge: document.getElementById("statusBadge"), + cancelBtn: document.getElementById("cancelBtn"), + editBtn: document.getElementById("editBtn"), + logoutBtn: document.getElementById("logoutBtn") +}; + +const eventId = new URLSearchParams(window.location.search).get("id"); + +let eventData = null; + +function setStatus(status) { + els.statusBadge.textContent = status; + els.statusBadge.className = `status-badge status-${status}`; +} + +async function loadEvent() { + const events = await apiRequest("/events"); + eventData = events.find(e => e._id === eventId); + + if (!eventData) { + els.name.textContent = "Event not found"; + return; + } + + els.name.textContent = eventData.name; + els.image.src = eventData.image; + els.description.textContent = eventData.description; + + els.time.textContent = `${eventData.startTime} - ${eventData.endTime}`; + els.date.textContent = eventData.date; + els.location.textContent = eventData.location?.venue + ", " + eventData.location?.city; + + setStatus(eventData.status); + + // Requirements + els.requirements.innerHTML = eventData.requirements + .map(r => `
    • ${r}
    • `) + .join(""); + + // Stats + els.totalSlots.textContent = eventData.volunteerSlots; + + await loadVolunteers(); +} + +async function loadVolunteers() { + const data = await apiRequest(`/applications/events/${eventId}/registrations`); + + const volunteers = Array.isArray(data) ? data : data.data || []; + + els.registered.textContent = volunteers.length; + els.remaining.textContent = eventData.volunteerSlots - volunteers.length; + + if (!volunteers.length) { + els.table.innerHTML = `No volunteers yet`; + return; + } + + els.table.innerHTML = volunteers.map(v => ` + + ${v.user?.fullName || "Unknown"} + ${v.user?.email || "—"} + ${new Date(v.createdAt).toDateString()} + Confirmed + + `).join(""); +} + +// Cancel event +els.cancelBtn.addEventListener("click", async () => { + if (!confirm("Cancel this event?")) return; + + await apiRequest(`/events/${eventId}/cancel`, { + method: "PATCH", + body: JSON.stringify({ reason: "Cancelled by organizer" }) + }); + + alert("Event cancelled"); + location.reload(); +}); + +// Edit +els.editBtn.addEventListener("click", () => { + window.location.href = `create-event.html?id=${eventId}`; +}); + +// Logout +els.logoutBtn.addEventListener("click", () => { + logout("../login/login.html"); +}); + +// Init +document.addEventListener("DOMContentLoaded", async () => { + await requireRole("organizer", "../login/login.html"); + await loadEvent(); +}); + + + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireOrganizer } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { getQueryParam, formatDate, getLocationText } from "../../assets/js/utils.js"; + +// const eventId = getQueryParam("id"); +// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); +// document.getElementById("cancelLink").addEventListener("click", (e) => { +// e.preventDefault(); +// window.location.href = `${ROUTES.organizerCancelEvent}?id=${encodeURIComponent(eventId)}`; +// }); + +// document.addEventListener("DOMContentLoaded", async () => { +// await requireOrganizer(); +// try { +// const payload = await apiRequest("/events"); +// const events = normalizeArray(payload, ["events"]); +// const event = events.find((e) => String(e._id || e.id) === String(eventId)); +// document.getElementById("eventName").textContent = event?.name || "Event not found"; +// document.getElementById("eventDescription").textContent = event?.description || ""; +// document.getElementById("eventDate").textContent = formatDate(event?.date, "long"); +// document.getElementById("eventLocation").textContent = getLocationText(event); + +// const regsPayload = await apiRequest(`/applications/events/${eventId}/registrations`); +// const regs = Array.isArray(regsPayload) ? regsPayload : regsPayload.data || []; +// document.getElementById("volunteerTable").innerHTML = regs.map((v) => ` +// +// ${v.user?.fullName || "Unknown"} +// ${v.user?.email || "—"} +// ${formatDate(v.createdAt, "long")} +// ${v.attendance || "confirmed"} +// +// `).join("") || `No volunteers yet.`; +// } catch { +// document.getElementById("volunteerTable").innerHTML = `Failed to load event details.`; +// } +// }); diff --git a/Organizer/events/event-listing.css b/Organizer/events/event-listing.css new file mode 100644 index 0000000..95da102 --- /dev/null +++ b/Organizer/events/event-listing.css @@ -0,0 +1,254 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Poppins", sans-serif; +} + +body { + background: #f4f7fb; + color: #1f2f46; +} + +.dashboard-layout { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 15rem; + background: #1f3b63; + color: #fff; + padding: 1.5rem 1rem; + flex-shrink: 0; +} + +.sidebar-logo { + text-align: center; + margin-bottom: 2rem; +} + +.sidebar-logo img { + width: 6rem; +} + +.sidebar-menu { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.55rem; +} + +.sidebar-menu li a, +.sidebar-logout { + width: 100%; + display: flex; + align-items: center; + gap: 0.85rem; + color: #fff; + text-decoration: none; + padding: 0.95rem 1rem; + border-radius: 0.65rem; + font-size: 0.98rem; + border: none; + background: transparent; + cursor: pointer; +} + +.sidebar-menu li.active a, +.sidebar-menu li a:hover, +.sidebar-logout:hover { + background: #1f80ea; +} + +.main-content { + flex: 1; +} + +.topbar { + background: #fff; + padding: 1.5rem 2rem; + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.page-title { + font-size: 2rem; + font-weight: 700; + color: #223f6b; +} + +.page-subtitle { + color: #6b7280; + font-size: 0.9rem; + margin-top: 0.3rem; +} + +.topbar-actions { + display: flex; + align-items: center; + gap: 1rem; +} + +.create-event-btn { + background: #223f6b; + color: #fff; + text-decoration: none; + padding: 0.95rem 1.4rem; + border-radius: 0.75rem; + font-weight: 600; +} + +.icon-btn { + width: 2.6rem; + height: 2.6rem; + border: none; + border-radius: 50%; + background: transparent; + color: #223f6b; + font-size: 1.35rem; + cursor: pointer; +} + +.profile-mini img { + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + object-fit: cover; +} + +.events-section { + padding: 2rem; +} + +.filter-tabs { + display: flex; + gap: 2rem; + margin-bottom: 1.5rem; + flex-wrap: wrap; +} + +.filter-btn { + background: transparent; + border: none; + color: #111827; + font-size: 1rem; + cursor: pointer; + padding-bottom: 0.4rem; +} + +.filter-btn.active { + color: #1f80ea; + font-weight: 600; +} + +.table-wrapper { + background: #fff; + border-radius: 0.85rem; + overflow: hidden; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #eef2f6; +} + +th, +td { + padding: 1rem; + text-align: left; + font-size: 0.92rem; +} + +th { + color: #35527a; + font-weight: 600; +} + +tbody tr { + border-bottom: 0.08rem solid #d7dde6; +} + +tbody tr:last-child { + border-bottom: none; +} + +.status-badge { + display: inline-block; + min-width: 7rem; + text-align: center; + padding: 0.62rem 1rem; + border-radius: 0.45rem; + color: #fff; + font-size: 0.88rem; + font-weight: 600; +} + +.status-upcoming { + background: #f5ae42; +} + +.status-published { + background: #26a65b; +} + +.status-draft { + background: #6f809b; +} + +.status-completed { + background: #1f80ea; +} + +.status-cancelled { + background: #dc2626; +} + +.row-actions { + text-align: center; +} + +.row-actions a, +.row-actions button { + color: #223f6b; + background: transparent; + border: none; + cursor: pointer; + font-size: 1.2rem; +} + +@media (max-width: 52rem) { + .dashboard-layout { + flex-direction: column; + } + + .sidebar { + width: 100%; + } + + .topbar { + flex-direction: column; + gap: 1rem; + } + + .table-wrapper { + overflow-x: auto; + } + + table { + min-width: 50rem; + } +} + + + + + + + +/* Uses shared layout/tables/badges */ \ No newline at end of file diff --git a/Organizer/events/event-listing.html b/Organizer/events/event-listing.html new file mode 100644 index 0000000..7f524b6 --- /dev/null +++ b/Organizer/events/event-listing.html @@ -0,0 +1,131 @@ + + + + + + AIDLoop Organizer Events + + + + + +
      + + +
      +
      +
      +

      Events

      +

      Browse and Manage all your events

      +
      + +
      + Create Event + +
      + Organizer Avatar +
      +
      +
      + +
      +
      + + + + +
      + +
      + + + + + + + + + + + + + + + + + +
      Events NameLocationDateVolunteersStatus
      Loading events...
      +
      +
      +
      +
      + + + + + + + + + + + + + diff --git a/Organizer/events/event-listing.js b/Organizer/events/event-listing.js new file mode 100644 index 0000000..94395cd --- /dev/null +++ b/Organizer/events/event-listing.js @@ -0,0 +1,226 @@ +import { apiRequest, normalizeArray } from "../assets/js/api.js"; +import { requireRole } from "../assets/js/auth.js"; +import { formatDate } from "../assets/js/utils.js"; +import { ROUTES } from "../assets/js/config.js"; +import { logout } from "../assets/js/logout.js"; +import { bindFilterButtons } from "../assets/js/ui.js"; + +const els = { + organizerAvatar: document.getElementById("organizerAvatar"), + eventsTable: document.getElementById("eventsTable"), + filterButtons: document.querySelectorAll(".filter-btn"), + logoutBtn: document.getElementById("logoutBtn") +}; + +let organizer = null; +let allEvents = []; +let currentFilter = "all"; + +function getLocation(event) { + if (typeof event.location === "string" && event.location.trim()) { + return event.location; + } + + if (event.location && typeof event.location === "object") { + const venue = event.location.venue || ""; + const city = event.location.city || ""; + const state = event.location.state || ""; + return [venue, city || state].filter(Boolean).join(", ") || "—"; + } + + return event.city || "—"; +} + +function getEventStatus(event) { + const raw = String(event.status || "").toLowerCase(); + + if (raw === "cancelled" || raw === "canceled") return "cancelled"; + if (raw === "draft") return "draft"; + if (raw === "published") { + const eventDate = event.date ? new Date(event.date) : null; + const now = new Date(); + + if (eventDate && !Number.isNaN(eventDate.getTime()) && eventDate < now) { + return "completed"; + } + + return "published"; + } + + const eventDate = event.date ? new Date(event.date) : null; + const now = new Date(); + + if (eventDate && !Number.isNaN(eventDate.getTime()) && eventDate < now) { + return "completed"; + } + + return "upcoming"; +} + +function getStatusText(status) { + if (status === "published") return "Published"; + if (status === "draft") return "Draft"; + if (status === "completed") return "Completed"; + if (status === "cancelled") return "Cancelled"; + return "Upcoming"; +} + +function getVolunteerCount(event) { + const filled = + event.filledSlots ?? + event.registrationsCount ?? + event.registeredCount ?? + event.attendeesCount ?? + 0; + + const total = + event.volunteerSlots ?? + event.totalSlots ?? + event.capacity ?? + 0; + + return `${filled}/${total}`; +} + +function renderEvents() { + let filtered = [...allEvents]; + + if (currentFilter !== "all") { + filtered = filtered.filter((event) => getEventStatus(event) === currentFilter); + } + + filtered.sort((a, b) => new Date(b.createdAt || b.date || 0) - new Date(a.createdAt || a.date || 0)); + + if (!filtered.length) { + els.eventsTable.innerHTML = ` + + No events found. + + `; + return; + } + + els.eventsTable.innerHTML = filtered.map((event) => { + const status = getEventStatus(event); + const id = event._id || event.id || ""; + + return ` + + ${event.name || event.title || "Untitled Event"} + ${getLocation(event)} + ${formatDate(event.date, "long")} + ${getVolunteerCount(event)} + + + ${getStatusText(status)} + + + + + + + + + `; + }).join(""); +} + +async function loadEvents() { + const payload = await apiRequest("/events"); + const events = normalizeArray(payload, ["events"]); + + const organizerId = String(organizer._id || organizer.id || ""); + + allEvents = events.filter((event) => { + if (typeof event.organizer === "object" && event.organizer) { + return String(event.organizer._id || event.organizer.id || "") === organizerId; + } + + return String(event.organizerId || "") === organizerId; + }); + + renderEvents(); +} + +document.addEventListener("DOMContentLoaded", async () => { + organizer = await requireRole("organizer", ROUTES.organizerLogin); + if (!organizer) return; + + if (organizer.profileImage) { + els.organizerAvatar.src = organizer.profileImage; + } + + bindFilterButtons(els.filterButtons, (filter) => { + currentFilter = filter; + renderEvents(); + }); + + els.logoutBtn.addEventListener("click", () => { + logout(ROUTES.organizerLogin); + }); + + try { + await loadEvents(); + } catch (error) { + els.eventsTable.innerHTML = ` + + Failed to load events. + + `; + } +}); + + + + + + + + + +// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; +// import { requireOrganizer } from "../../assets/js/auth.js"; +// import { logout } from "../../assets/js/logout.js"; +// import { ROUTES } from "../../assets/js/config.js"; +// import { formatDate, getLocationText } from "../../assets/js/utils.js"; + +// const table = document.getElementById("eventsTable"); +// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); + +// function statusOf(event) { +// const raw = String(event.status || "").toLowerCase(); +// if (raw === "cancelled" || raw === "canceled") return "cancelled"; +// if (raw === "draft") return "draft"; +// if (raw === "published" && event.date && new Date(event.date) < new Date()) return "completed"; +// return raw || "published"; +// } + +// document.addEventListener("DOMContentLoaded", async () => { +// const organizer = await requireOrganizer(); +// if (!organizer) return; + +// try { +// const payload = await apiRequest("/events"); +// const events = normalizeArray(payload, ["events"]); +// const organizerId = String(organizer._id || organizer.id || ""); +// const own = events.filter((event) => { +// if (typeof event.organizer === "object" && event.organizer) { +// return String(event.organizer._id || event.organizer.id || "") === organizerId; +// } +// return String(event.organizerId || "") === organizerId; +// }); + +// table.innerHTML = own.map((event) => ` +// +// ${event.name || "Untitled Event"} +// ${getLocationText(event)} +// ${formatDate(event.date, "long")} +// ${event.filledSlots ?? event.registrationsCount ?? 0}/${event.volunteerSlots ?? 0} +// ${statusOf(event)} +// Details +// +// `).join("") || `No events found.`; +// } catch { +// table.innerHTML = `Failed to load events.`; +// } +// }); diff --git a/Organizer/login/organizer-login.css b/Organizer/login/organizer-login.css new file mode 100644 index 0000000..5a91004 --- /dev/null +++ b/Organizer/login/organizer-login.css @@ -0,0 +1,220 @@ +Reset +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Poppins", sans-serif; +} + +/* Page */ +body { + background: #f5f7fa; + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; +} + +/* Container */ +.login-page { + width: 100%; + display: flex; + justify-content: center; + padding: 20px; +} + +/* Card */ +.login-card { + background: #ffffff; + padding: 40px 35px; + border-radius: 18px; + width: 100%; + max-width: 500px; + text-align: center; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.06); +} + +/* Logo */ +.logo-wrap { + margin-bottom: 20px; +} + +.logo { + width: 90px; + margin-bottom: 10px; +} + +.logo-wrap p { + font-size: 14px; + color: #6b7280; +} + +/* Title */ +.login-card h1 { + font-size: 24px; + font-weight: 600; + margin-bottom: 10px; + color: #111827; +} + +.subtitle { + font-size: 14px; + color: #6b7280; + margin-bottom: 25px; + line-height: 1.5; +} + +/* Form */ +.form-group { + text-align: left; + margin-bottom: 20px; +} + +.form-group label { + font-size: 14px; + font-weight: 500; + margin-bottom: 6px; + display: block; + color: #374151; +} + +/* Inputs */ +.form-group input { + width: 100%; + padding: 14px 16px; + border-radius: 10px; + border: 1px solid #d1d5db; + font-size: 14px; + outline: none; + transition: 0.2s ease; +} + +.form-group input:focus { + border-color: #2563eb; + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); +} + +/* Password */ +.password-wrap { + position: relative; +} + +.password-wrap input { + padding-right: 45px; +} + +.password-wrap button { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + cursor: pointer; + color: #6b7280; + font-size: 16px; +} + +/* Button */ +.btn-primary { + width: 100%; + background: #1f3b63; + color: #fff; + padding: 15px; + border: none; + border-radius: 12px; + font-size: 15px; + font-weight: 500; + cursor: pointer; + margin-top: 10px; + transition: 0.3s; +} + +.btn-primary:hover { + background: #173052; +} + +/* Links */ +.links { + margin-top: 18px; + font-size: 13px; + color: #6b7280; + display: flex; + justify-content: center; + gap: 10px; +} + +.links a { + text-decoration: none; + color: #2563eb; + font-weight: 500; +} + +.links a:hover { + text-decoration: underline; +} + +/* Bottom text */ +.register-text { + margin-top: 15px; + font-size: 13px; + color: #6b7280; +} + +.register-text a { + color: #2563eb; + font-weight: 500; + text-decoration: none; +} + +.register-text a:hover { + text-decoration: underline; +} + +/* Errors */ +.error-message { + color: #dc2626; + font-size: 12px; + margin-top: 4px; +} + +/* Responsive */ +@media (max-width: 480px) { + .login-card { + padding: 30px 20px; + } + + .login-card h1 { + font-size: 20px; + } +} + + + + + + + + +/* +.auth-wrap { min-height: + 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.auth-card { + width: 100%; + max-width: 32rem; + background: #fff; + border-radius: 1rem; + padding: 2rem; + box-shadow: 0 0.5rem 2rem rgba(0,0,0,0.06); +} + +.auth-card h1 { + margin-bottom: 1.2rem; + color: #223f6b; +} */ diff --git a/Organizer/login/organizer-login.html b/Organizer/login/organizer-login.html new file mode 100644 index 0000000..a525105 --- /dev/null +++ b/Organizer/login/organizer-login.html @@ -0,0 +1,108 @@ + + + + + + AIDLoop Organizer Login + + + + + + + + +
      +
      @@ -56,7 +91,7 @@

      Loading...

      Events Description

      -
      --> +
      @@ -113,24 +148,3 @@

      0

      - - - - - - - - - - diff --git a/Organizer/events/event-details.js b/Organizer/events/event-details.js index ffdccaf..c318b56 100644 --- a/Organizer/events/event-details.js +++ b/Organizer/events/event-details.js @@ -1,6 +1,6 @@ -import { apiRequest } from "../assets/js/api.js"; -import { requireRole } from "../assets/js/auth.js"; -import { logout } from "../assets/js/logout.js"; +import { apiRequest } from "../../assets/js/api.js"; +import { requireRole } from "../../assets/js/auth.js"; +import { logout } from "../../assets/js/logout.js"; const els = { name: document.getElementById("eventName"), @@ -102,60 +102,12 @@ els.editBtn.addEventListener("click", () => { // Logout els.logoutBtn.addEventListener("click", () => { - logout("../login/login.html"); + logout("../login/organizer-login.html/"); + }); // Init document.addEventListener("DOMContentLoaded", async () => { - await requireRole("organizer", "../login/login.html"); + await requireRole("organizer", "../login/organizer-login.html"); await loadEvent(); }); - - - - - - - - - - - -// import { apiRequest, normalizeArray } from "../../assets/js/api.js"; -// import { requireOrganizer } from "../../assets/js/auth.js"; -// import { logout } from "../../assets/js/logout.js"; -// import { ROUTES } from "../../assets/js/config.js"; -// import { getQueryParam, formatDate, getLocationText } from "../../assets/js/utils.js"; - -// const eventId = getQueryParam("id"); -// document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home)); -// document.getElementById("cancelLink").addEventListener("click", (e) => { -// e.preventDefault(); -// window.location.href = `${ROUTES.organizerCancelEvent}?id=${encodeURIComponent(eventId)}`; -// }); - -// document.addEventListener("DOMContentLoaded", async () => { -// await requireOrganizer(); -// try { -// const payload = await apiRequest("/events"); -// const events = normalizeArray(payload, ["events"]); -// const event = events.find((e) => String(e._id || e.id) === String(eventId)); -// document.getElementById("eventName").textContent = event?.name || "Event not found"; -// document.getElementById("eventDescription").textContent = event?.description || ""; -// document.getElementById("eventDate").textContent = formatDate(event?.date, "long"); -// document.getElementById("eventLocation").textContent = getLocationText(event); - -// const regsPayload = await apiRequest(`/applications/events/${eventId}/registrations`); -// const regs = Array.isArray(regsPayload) ? regsPayload : regsPayload.data || []; -// document.getElementById("volunteerTable").innerHTML = regs.map((v) => ` -// -// ${v.user?.fullName || "Unknown"} -// ${v.user?.email || "—"} -// ${formatDate(v.createdAt, "long")} -// ${v.attendance || "confirmed"} -// -// `).join("") || `No volunteers yet.`; -// } catch { -// document.getElementById("volunteerTable").innerHTML = `Failed to load event details.`; -// } -// }); diff --git a/Organizer/events/event-listing.css b/Organizer/events/event-listing.css index 95da102..93e73cc 100644 --- a/Organizer/events/event-listing.css +++ b/Organizer/events/event-listing.css @@ -244,11 +244,3 @@ tbody tr:last-child { min-width: 50rem; } } - - - - - - - -/* Uses shared layout/tables/badges */ \ No newline at end of file diff --git a/Organizer/events/event-listing.html b/Organizer/events/event-listing.html index 7f524b6..54d5ba7 100644 --- a/Organizer/events/event-listing.html +++ b/Organizer/events/event-listing.html @@ -4,7 +4,6 @@ AIDLoop Organizer Events -
      - - + @@ -63,9 +63,9 @@

      Welcome back to AidLoop

      Create organization account - --> + - + - diff --git a/Organizer/login/organizer-login.js b/Organizer/login/organizer-login.js index a1e37f3..59c2685 100644 --- a/Organizer/login/organizer-login.js +++ b/Organizer/login/organizer-login.js @@ -1,5 +1,5 @@ -import { apiRequest } from "../assets/js/api.js"; -import { ROUTES } from "../assets/js/config.js"; +import { apiRequest } from "../../assets/js/api.js"; +import { ROUTES } from "../../assets/js/config.js"; const els = { form: document.getElementById("loginForm"), @@ -90,44 +90,3 @@ els.form.addEventListener("submit", async (e) => { setLoading(false); } }); - - - - - - - - -// import { apiRequest } from "../../assets/js/api.js"; -// import { ROUTES } from "../../assets/js/config.js"; - -// const form = document.getElementById("loginForm"); -// const email = document.getElementById("email"); -// const password = document.getElementById("password"); -// const formError = document.getElementById("formError"); -// const formSuccess = document.getElementById("formSuccess"); -// const loginBtn = document.getElementById("loginBtn"); - -// form.addEventListener("submit", async (e) => { -// e.preventDefault(); -// formError.textContent = ""; -// formSuccess.textContent = ""; - -// try { -// loginBtn.disabled = true; -// const response = await apiRequest("/auth/login", { -// method: "POST", -// body: JSON.stringify({ email: email.value.trim(), password: password.value.trim() }) -// }); -// if (String(response?.user?.role || "organizer").toLowerCase() !== "organizer") { -// throw new Error("Not an organizer account"); -// } -// localStorage.setItem("aidloop_organizer_email", email.value.trim()); -// formSuccess.textContent = response.message || "Login successful"; -// setTimeout(() => { window.location.href = ROUTES.organizerDashboard; }, 800); -// } catch (err) { -// formError.textContent = err.message || "Login failed"; -// } finally { -// loginBtn.disabled = false; -// } -// }); diff --git a/Organizer/profile/organizer-profile.css b/Organizer/profile/organizer-profile.css index e21d975..211b5a9 100644 --- a/Organizer/profile/organizer-profile.css +++ b/Organizer/profile/organizer-profile.css @@ -380,28 +380,3 @@ body { flex-direction: column; } } - - - - - - - - - -/* .profile-card { - max-width: 50rem; - background: #fff; - border-radius: 1rem; - padding: 1.5rem; -} - -.profile-card h2 { - color: #223f6b; - margin-bottom: 1rem; -} - -#profileMessage { - display: block; - margin-top: 0.5rem; -} */ diff --git a/Organizer/profile/organizer-profile.html b/Organizer/profile/organizer-profile.html index 4c0ba7e..00bdc49 100644 --- a/Organizer/profile/organizer-profile.html +++ b/Organizer/profile/organizer-profile.html @@ -5,7 +5,7 @@ AIDLoop Organization Profile - + - !-- Main Content -- +