diff --git a/src/data/api.js b/src/data/api.js new file mode 100644 index 0000000..2bd77fd --- /dev/null +++ b/src/data/api.js @@ -0,0 +1,82 @@ +// ======================================== +// OpenScholar — API Client +// Central fetch() wrapper with demo credential auto-login +// ======================================== + +const API_BASE = 'http://localhost:8000'; + +const DEMO_CREDENTIALS = { + admin: { email: 'admin@demo.openscholar.org', password: 'demo123' }, + ngo: { email: 'ngo@demo.openscholar.org', password: 'demo123' }, + donor: { email: 'donor@demo.openscholar.org', password: 'demo123' }, + school: { email: 'school@demo.openscholar.org', password: 'demo123' }, + student: { email: 'student@demo.openscholar.org', password: 'demo123' }, +}; + +// In-memory token cache — resets on page refresh (fine for demo) +const tokenCache = {}; +let _currentRole = 'public'; + +async function getToken(role) { + if (!DEMO_CREDENTIALS[role]) return null; // public: no token + if (tokenCache[role]) return tokenCache[role]; // cached: reuse + // Auto-login with demo credentials + try { + const res = await fetch(`${API_BASE}/api/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(DEMO_CREDENTIALS[role]), + }); + if (!res.ok) { + console.error(`Login failed for role ${role}:`, res.status); + return null; + } + const data = await res.json(); + tokenCache[role] = data.accessToken; // camelCase from backend + return tokenCache[role]; + } catch (err) { + console.error(`Network error during login for role ${role}:`, err); + return null; + } +} + +export const api = { + setRole(role) { + _currentRole = role; + }, + + async get(path, role) { + const r = role ?? _currentRole; + const token = await getToken(r); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + try { + const res = await fetch(`${API_BASE}${path}`, { headers }); + if (!res.ok) { + console.error(`GET ${path} failed:`, res.status); + return []; + } + return res.json(); + } catch (err) { + console.error(`Network error GET ${path}:`, err); + return []; + } + }, + + async post(path, body, role) { + const r = role ?? _currentRole; + const token = await getToken(r); + const headers = { 'Content-Type': 'application/json' }; + if (token) headers.Authorization = `Bearer ${token}`; + try { + const res = await fetch(`${API_BASE}${path}`, { + method: 'POST', + headers, + body: JSON.stringify(body), + }); + return res.json(); + } catch (err) { + console.error(`Network error POST ${path}:`, err); + return {}; + } + }, +}; diff --git a/src/data/mock.js b/src/data/mock.js index 68e6977..9a8aa83 100644 --- a/src/data/mock.js +++ b/src/data/mock.js @@ -1,375 +1,94 @@ // ======================================== -// OpenScholar — Mock Data +// OpenScholar — Mock Data → Async API Client +// All exports are now async functions fetching from the live backend. +// Import names have changed: use getNGOs() instead of ngos, etc. // ======================================== -export const ngos = [ - { - id: 'ngo-1', - name: 'Bright Future Foundation', - location: 'Kathmandu, Nepal', - status: 'verified', - description: 'Empowering underprivileged children through quality education and skill development programs across rural Nepal.', - taxDoc: 'TaxClearance_2025.pdf', - regDoc: 'CompanyReg_BFF.pdf', - avatar: 'BF', - color: '#10b981', - totalFunded: 245000, - studentsHelped: 342, - programsCount: 5, - registeredDate: '2024-06-15', - programs: ['prog-1', 'prog-2'], - }, - { - id: 'ngo-2', - name: 'EduHope International', - location: 'Pokhara, Nepal', - status: 'verified', - description: 'Providing scholarships and educational resources to girls in remote mountain communities.', - taxDoc: 'TaxClearance_EHI_2025.pdf', - regDoc: 'Registration_EHI.pdf', - avatar: 'EH', - color: '#3b82f6', - totalFunded: 189000, - studentsHelped: 218, - programsCount: 3, - registeredDate: '2024-08-20', - programs: ['prog-3'], - }, - { - id: 'ngo-3', - name: 'Children First Nepal', - location: 'Lalitpur, Nepal', - status: 'pending', - description: 'Working towards eliminating child labor by providing free education and nutrition support.', - taxDoc: 'TaxDoc_CFN_2025.pdf', - regDoc: 'Registration_CFN.pdf', - avatar: 'CF', - color: '#f59e0b', - totalFunded: 0, - studentsHelped: 0, - programsCount: 0, - registeredDate: '2025-02-10', - programs: [], - }, - { - id: 'ngo-4', - name: 'Nepal Education Alliance', - location: 'Biratnagar, Nepal', - status: 'verified', - description: 'Building schools, training teachers, and creating sustainable education ecosystems in eastern Nepal.', - taxDoc: 'Tax_NEA_2025.pdf', - regDoc: 'Reg_NEA.pdf', - avatar: 'NE', - color: '#a855f7', - totalFunded: 156000, - studentsHelped: 195, - programsCount: 4, - registeredDate: '2024-04-01', - programs: ['prog-4', 'prog-5'], - }, - { - id: 'ngo-5', - name: 'Learn & Grow Trust', - location: 'Chitwan, Nepal', - status: 'rejected', - description: 'Supporting digital literacy programs for disadvantaged youth.', - taxDoc: 'Tax_LGT.pdf', - regDoc: 'Reg_LGT.pdf', - avatar: 'LG', - color: '#ef4444', - totalFunded: 0, - studentsHelped: 0, - programsCount: 0, - registeredDate: '2025-01-22', - programs: [], - }, -]; +import { api } from './api.js'; -export const programs = [ - { - id: 'prog-1', - ngoId: 'ngo-1', - name: 'Girls Education Program 2026', - description: 'Comprehensive scholarship covering tuition, books, and uniforms for girls in rural Nepal. Targeting 200 students across 15 schools.', - status: 'active', - categories: ['tuition', 'books', 'uniforms', 'stationery'], - totalBudget: 150000, - allocated: 98000, - studentsEnrolled: 145, - startDate: '2026-01-01', - endDate: '2026-12-31', - }, - { - id: 'prog-2', - ngoId: 'ngo-1', - name: 'STEM Scholarship 2025', - description: 'Providing STEM education resources and tuition support for promising students from low-income families.', - status: 'completed', - categories: ['tuition', 'books', 'lab equipment'], - totalBudget: 95000, - allocated: 95000, - studentsEnrolled: 78, - startDate: '2025-01-01', - endDate: '2025-12-31', - }, - { - id: 'prog-3', - ngoId: 'ngo-2', - name: 'Mountain Girls Scholarship', - description: 'Supporting education for girls in remote Himalayan communities with full scholarship coverage.', - status: 'active', - categories: ['tuition', 'books', 'uniforms', 'food', 'transport'], - totalBudget: 120000, - allocated: 67000, - studentsEnrolled: 92, - startDate: '2026-01-01', - endDate: '2026-12-31', - }, - { - id: 'prog-4', - ngoId: 'ngo-4', - name: 'Eastern Nepal Literacy Drive', - description: 'Adult and child literacy program combined with formal education support in eastern districts.', - status: 'active', - categories: ['tuition', 'books', 'stationery'], - totalBudget: 80000, - allocated: 45000, - studentsEnrolled: 110, - startDate: '2026-02-01', - endDate: '2026-12-31', - }, - { - id: 'prog-5', - ngoId: 'ngo-4', - name: 'Teacher Training Initiative', - description: 'Training local teachers and providing educational infrastructure support to community schools.', - status: 'active', - categories: ['training', 'infrastructure', 'materials'], - totalBudget: 60000, - allocated: 32000, - studentsEnrolled: 0, - startDate: '2026-03-01', - endDate: '2026-11-30', - }, -]; +// ---- Helpers ---- -export const students = [ - { - id: 'stu-1', - name: 'Aarati Tamang', - age: 14, - school: 'Shree Janapriya Secondary School', - grade: 8, - guardian: 'Sita Tamang (Mother)', - programId: 'prog-1', - ngoId: 'ngo-1', - scholarshipId: 'EDU-2026-00142', - walletBalance: 1200, - totalReceived: 3500, - status: 'active', - location: 'Sindhupalchok', - }, - { - id: 'stu-2', - name: 'Binod Shrestha', - age: 12, - school: 'Himalayan Model School', - grade: 6, - guardian: 'Ram Shrestha (Father)', - programId: 'prog-1', - ngoId: 'ngo-1', - scholarshipId: 'EDU-2026-00143', - walletBalance: 800, - totalReceived: 2800, - status: 'active', - location: 'Dolakha', - }, - { - id: 'stu-3', - name: 'Chandra Maya Gurung', - age: 15, - school: 'Annapurna Secondary School', - grade: 9, - guardian: 'Devi Gurung (Grandmother)', - programId: 'prog-3', - ngoId: 'ngo-2', - scholarshipId: 'EDU-2026-00089', - walletBalance: 2000, - totalReceived: 4500, - status: 'active', - location: 'Manang', - }, - { - id: 'stu-4', - name: 'Deepa Rai', - age: 13, - school: 'Koshi Valley School', - grade: 7, - guardian: 'Kumar Rai (Father)', - programId: 'prog-4', - ngoId: 'ngo-4', - scholarshipId: 'EDU-2026-00201', - walletBalance: 600, - totalReceived: 2100, - status: 'active', - location: 'Sunsari', - }, - { - id: 'stu-5', - name: 'Ekta Sharma', - age: 16, - school: 'Saraswati Higher Secondary', - grade: 10, - guardian: 'Hari Sharma (Father)', - programId: 'prog-1', - ngoId: 'ngo-1', - scholarshipId: 'EDU-2026-00144', - walletBalance: 0, - totalReceived: 3200, - status: 'graduated', - location: 'Nuwakot', - }, -]; +export function relativeTime(isoString) { + const diff = Date.now() - new Date(isoString).getTime(); + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`; + if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`; + if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; + return 'just now'; +} -export const donors = [ - { id: 'donor-1', name: 'Sarah Mitchell', totalDonated: 15000, donations: 8 }, - { id: 'donor-2', name: 'James Chen', totalDonated: 25000, donations: 12 }, - { id: 'donor-3', name: 'Priya Patel', totalDonated: 8000, donations: 5 }, - { id: 'donor-4', name: 'Nordic Aid Foundation', totalDonated: 75000, donations: 3 }, - { id: 'donor-5', name: 'Global Ed Trust', totalDonated: 120000, donations: 6 }, -]; +// ---- Public endpoints (no auth) ---- -export const donations = [ - { id: 'don-1', donorId: 'donor-1', ngoId: 'ngo-1', programId: 'prog-1', amount: 5000, date: '2026-02-15', type: 'program' }, - { id: 'don-2', donorId: 'donor-2', ngoId: 'ngo-2', programId: 'prog-3', amount: 10000, date: '2026-02-20', type: 'program' }, - { id: 'don-3', donorId: 'donor-3', ngoId: 'ngo-1', programId: null, amount: 3000, date: '2026-03-01', type: 'general' }, - { id: 'don-4', donorId: 'donor-4', ngoId: 'ngo-4', programId: 'prog-4', amount: 25000, date: '2026-01-10', type: 'program' }, - { id: 'don-5', donorId: 'donor-5', ngoId: 'ngo-1', programId: 'prog-1', amount: 50000, date: '2026-01-25', type: 'program' }, - { id: 'don-6', donorId: 'donor-1', ngoId: 'ngo-2', programId: 'prog-3', amount: 2500, date: '2026-03-03', type: 'student', studentId: 'stu-3' }, - { id: 'don-7', donorId: 'donor-2', ngoId: 'ngo-4', programId: 'prog-5', amount: 8000, date: '2026-02-28', type: 'program' }, -]; +export async function getPlatformStats() { + return await api.get('/api/public/stats', 'public'); +} -export const invoices = [ - { - id: 'inv-1', - schoolId: 'school-1', - schoolName: 'Shree Janapriya Secondary School', - ngoId: 'ngo-1', - programId: 'prog-1', - amount: 12500, - category: 'tuition', - status: 'approved', - items: [ - { desc: 'Tuition fee - Grade 8 (15 students)', amount: 7500 }, - { desc: 'Tuition fee - Grade 9 (10 students)', amount: 5000 }, - ], - date: '2026-02-01', - approvedDate: '2026-02-05', - }, - { - id: 'inv-2', - schoolId: 'school-1', - schoolName: 'Shree Janapriya Secondary School', - ngoId: 'ngo-1', - programId: 'prog-1', - amount: 4200, - category: 'uniforms', - status: 'pending', - items: [ - { desc: 'Winter uniforms (25 students)', amount: 3000 }, - { desc: 'Sports uniforms (20 students)', amount: 1200 }, - ], - date: '2026-03-01', - approvedDate: null, - }, - { - id: 'inv-3', - schoolId: 'school-2', - schoolName: 'Annapurna Secondary School', - ngoId: 'ngo-2', - programId: 'prog-3', - amount: 8700, - category: 'tuition', - status: 'approved', - items: [ - { desc: 'Tuition fee - Grade 7-9 (20 students)', amount: 6000 }, - { desc: 'Lab fee (20 students)', amount: 1200 }, - { desc: 'Library fee (20 students)', amount: 1500 }, - ], - date: '2026-01-15', - approvedDate: '2026-01-20', - }, - { - id: 'inv-4', - schoolId: 'school-3', - schoolName: 'Koshi Valley School', - ngoId: 'ngo-4', - programId: 'prog-4', - amount: 3500, - category: 'books', - status: 'pending', - items: [ - { desc: 'Textbooks Grade 6-8 (30 students)', amount: 2500 }, - { desc: 'Notebooks and stationery', amount: 1000 }, - ], - date: '2026-03-02', - approvedDate: null, - }, -]; +export async function getRecentActivity() { + return await api.get('/api/public/activity', 'public'); +} -export const schools = [ - { - id: 'school-1', - name: 'Shree Janapriya Secondary School', - location: 'Sindhupalchok', - status: 'verified', - studentsInPrograms: 25, - totalInvoiced: 16700, - }, - { - id: 'school-2', - name: 'Annapurna Secondary School', - location: 'Manang', - status: 'verified', - studentsInPrograms: 20, - totalInvoiced: 8700, - }, - { - id: 'school-3', - name: 'Koshi Valley School', - location: 'Sunsari', - status: 'pending', - studentsInPrograms: 30, - totalInvoiced: 3500, - }, - { - id: 'school-4', - name: 'Himalayan Model School', - location: 'Dolakha', - status: 'verified', - studentsInPrograms: 18, - totalInvoiced: 5200, - }, -]; +export async function getNGOs() { + return await api.get('/api/public/ngos', 'public'); +} -export const recentActivity = [ - { type: 'donation', color: 'green', text: 'Sarah Mitchell donated $2,500 to Mountain Girls Scholarship', time: '2 hours ago' }, - { type: 'invoice', color: 'amber', text: 'Shree Janapriya School submitted invoice for winter uniforms ($4,200)', time: '5 hours ago' }, - { type: 'verify', color: 'blue', text: "Children First Nepal's registration is pending verification", time: '1 day ago' }, - { type: 'allocation', color: 'green', text: 'Bright Future Foundation allocated $3,500 to Aarati Tamang', time: '1 day ago' }, - { type: 'invoice', color: 'green', text: 'Tuition invoice from Annapurna Secondary School approved', time: '2 days ago' }, - { type: 'program', color: 'blue', text: 'Teacher Training Initiative program launched by Nepal Education Alliance', time: '3 days ago' }, - { type: 'donation', color: 'green', text: 'James Chen donated $8,000 to Teacher Training Initiative', time: '3 days ago' }, - { type: 'blacklist', color: 'red', text: "Learn & Grow Trust's application was rejected due to incomplete documents", time: '5 days ago' }, -]; +export async function getPrograms() { + return await api.get('/api/public/programs', 'public'); +} -// Aggregate stats -export const platformStats = { - totalDonations: 590000, - totalStudents: 755, - totalNGOs: 12, - totalPrograms: 18, - totalSchools: 45, - fundsAllocated: 520000, - fundsUtilized: 437000, -}; +// ---- Admin endpoints ---- + +export async function getAdminDashboard() { + return await api.get('/api/admin/dashboard', 'admin'); +} + +export async function getAdminNGOs() { + return await api.get('/api/admin/ngos', 'admin'); +} + +export async function getBlacklist() { + return await api.get('/api/admin/blacklist', 'admin'); +} + +// ---- NGO endpoints ---- + +export async function getNGODashboard() { + return await api.get('/api/ngo/dashboard', 'ngo'); +} + +export async function getNGOPrograms() { + return await api.get('/api/ngo/programs', 'ngo'); +} + +export async function getNGOStudents() { + return await api.get('/api/ngo/students', 'ngo'); +} + +export async function getNGOInvoices() { + return await api.get('/api/ngo/invoices', 'ngo'); +} + +export async function getNGOAllocations() { + return await api.get('/api/ngo/allocations', 'ngo'); +} + +// ---- Donor endpoints ---- + +export async function getDonations() { + return await api.get('/api/donor/donations', 'donor'); +} + +// ---- School endpoints ---- + +export async function getSchoolInvoices() { + return await api.get('/api/school/invoices', 'school'); +} + +// ---- Student endpoints (use public programs) ---- + +export async function getStudents() { + // Students visible via NGO context — public programs for apply page + return await api.get('/api/public/programs', 'public'); +} diff --git a/src/main.js b/src/main.js index 0ed4fd9..fdb32c6 100644 --- a/src/main.js +++ b/src/main.js @@ -7,6 +7,7 @@ import './styles/components.css'; import './styles/pages.css'; import { registerRoute, initRouter, navigate } from './router.js'; +import { api } from './data/api.js'; import { renderNavbar, bindNavbarEvents } from './components/navbar.js'; import { renderSidebar } from './components/sidebar.js'; import { refreshIcons } from './icons.js'; @@ -55,23 +56,26 @@ const fullPageRoutes = ['/', '/public-dashboard']; function onRoleChange(role) { currentRole = role; + api.setRole(role); navigate(roleDefaultRoutes[role]); } -function renderPage(route) { +async function renderPage(route) { const role = getRoleFromRoute(route); currentRole = role; const isFullPage = fullPageRoutes.includes(route); if (isFullPage) { if (route === '/') { - app.innerHTML = renderNavbar(currentRole, onRoleChange, true) + renderLanding(); + const landingContent = await renderLanding(); + app.innerHTML = renderNavbar(currentRole, onRoleChange, true) + landingContent; } else if (route === '/public-dashboard') { + const dashboardContent = await renderPublicDashboard(); app.innerHTML = ` ${renderNavbar(currentRole, onRoleChange, false)}
There are no accounts on the blacklist
+${item.reason || 'No reason provided'}
Incomplete registration documents. Tax clearance document expired and company registration not verified by authorities.
-| Organization | Location | Registered | Status | Total Funded | Actions |
|---|---|---|---|---|---|
|
diff --git a/src/pages/donor/browse.js b/src/pages/donor/browse.js
index 9bc84bf..7199534 100644
--- a/src/pages/donor/browse.js
+++ b/src/pages/donor/browse.js
@@ -2,13 +2,15 @@
// OpenScholar — Donor: Browse NGOs, Programs & Scholarships
// ========================================
-import { ngos, programs, students } from '../../data/mock.js';
+import { getNGOs, getPrograms } from '../../data/mock.js';
import { icon } from '../../icons.js';
-export function renderDonorBrowse() {
+export async function renderDonorBrowse() {
+ const ngos = await getNGOs();
+ const programs = await getPrograms();
+
const verifiedNgos = ngos.filter(n => n.status === 'verified');
const activePrograms = programs.filter(p => p.status === 'active');
- const activeStudents = students.filter(s => s.status === 'active');
return `
`;
- }).join('')}
-
-
-
-
-
diff --git a/src/pages/donor/donate.js b/src/pages/donor/donate.js
index 9e435c3..4a40430 100644
--- a/src/pages/donor/donate.js
+++ b/src/pages/donor/donate.js
@@ -2,10 +2,13 @@
// OpenScholar — Donor: Donation Flow
// ========================================
-import { ngos, programs, students } from '../../data/mock.js';
+import { getNGOs, getPrograms } from '../../data/mock.js';
import { icon } from '../../icons.js';
-export function renderDonate() {
+export async function renderDonate() {
+ const ngos = await getNGOs();
+ const programs = await getPrograms();
+
const verifiedNgos = ngos.filter(n => n.status === 'verified');
return `
diff --git a/src/pages/donor/track.js b/src/pages/donor/track.js
index 3e291a3..fe37079 100644
--- a/src/pages/donor/track.js
+++ b/src/pages/donor/track.js
@@ -2,11 +2,16 @@
// OpenScholar — Donor: Track Funds
// ========================================
-import { donations, ngos, programs, students } from '../../data/mock.js';
+import { getDonations, getNGOs, getPrograms } from '../../data/mock.js';
import { icon } from '../../icons.js';
-export function renderDonorTrack() {
- const myDonations = donations.filter(d => d.donorId === 'donor-1');
+export async function renderDonorTrack() {
+ const donations = await getDonations();
+ const ngos = await getNGOs();
+ const programs = await getPrograms();
+
+ // API already returns only this donor's donations - no filtering needed
+ const myDonations = donations;
const totalDonated = myDonations.reduce((acc, d) => acc + d.amount, 0);
return `
@@ -54,11 +59,16 @@ export function renderDonorTrack() {
@@ -26,10 +28,6 @@ export function renderDonorBrowse() {
${icon('folder-open', 15)} Programs
${activePrograms.length}
-
@@ -110,38 +108,6 @@ export function renderDonorBrowse() {
| |||||
| + No donations yet. Browse NGOs to make your first donation. + | +|||||
| ${new Date(d.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} | diff --git a/src/pages/landing.js b/src/pages/landing.js index 89be762..002569b 100644 --- a/src/pages/landing.js +++ b/src/pages/landing.js @@ -1,7 +1,16 @@ -import { platformStats } from '../data/mock.js'; +import { getPlatformStats } from '../data/mock.js'; import { icon } from '../icons.js'; -export function renderLanding() { +export async function renderLanding() { + const platformStats = await getPlatformStats(); + + if (!platformStats.totalDonations) { + platformStats.totalDonations = 0; + platformStats.totalStudents = 0; + platformStats.totalNGOs = 0; + platformStats.totalSchools = 0; + } + return `|||||
| Date | Donor | Program | Amount | ||
| + No donations received yet + | +|||||
| ${new Date(d.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} | Anonymous Donor | -${prog ? prog.name : 'General Fund'} | +${d.programId ? 'Program' : 'General Fund'} | +$${d.amount.toLocaleString()} | |