diff --git a/e2e-auth-helper.cjs b/e2e-auth-helper.cjs index cc22161..8ec5872 100644 --- a/e2e-auth-helper.cjs +++ b/e2e-auth-helper.cjs @@ -31,7 +31,7 @@ const HUB_OF_TAB = { 'Coverage': 'Home', 'Assets': 'Estate', 'Faraid': 'Estate', 'Insurance': 'Estate', 'Wassiyah': 'Estate', 'Hibah': 'Estate', 'Family Waqf': 'Estate', 'Nominate': 'Estate', 'Claims (H2)': 'Estate', - 'Zakat': 'Giving', 'Sadaqah': 'Giving', 'Khairat': 'Giving', + 'Zakat': 'Giving', 'Sadaqah': 'Giving', 'Khairat': 'Giving', 'Support': 'Giving', 'Tree': 'Family', 'Trigger': 'Family', 'Mutawalli': 'Family', 'Manage': 'Family', 'Neighbourhood': 'Family', 'Prayer Times': 'Daily', 'Qibla': 'Daily', 'Quran': 'Daily', 'Locate': 'Daily' }; diff --git a/e2e-support.cjs b/e2e-support.cjs new file mode 100644 index 0000000..af21de7 --- /dev/null +++ b/e2e-support.cjs @@ -0,0 +1,86 @@ +// Verifies the Support tab: amount/frequency selection, and that "Support +// Now" actually opens a real, live Polar.sh checkout page (not a dead or +// placeholder link) — one check per frequency, since each maps to a +// different Polar product. +const { chromium } = require('playwright'); +const { signInFreshFamily, gotoTab } = require('./e2e-auth-helper.cjs'); +const BASE = 'https://moslem04.falahos.my/'; +const results = []; +const consoleErrors = []; +function record(name, pass, detail = '') { results.push({ name, pass, detail }); console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${detail ? ' — ' + detail : ''}`); } + +async function main() { + const browser = await chromium.launch(); + const page = await browser.newPage({ viewport: { width: 390, height: 844 } }); + page.on('console', m => { if (m.type() === 'error') consoleErrors.push(m.text()); }); + page.on('pageerror', e => consoleErrors.push(e.message)); + + await signInFreshFamily(page, BASE, 'e2e-support'); + await gotoTab(page, 'Support'); + await page.waitForTimeout(600); + + record('Support: intro copy renders', await page.locator('h3', { hasText: 'Support Nur Falah' }).isVisible().catch(() => false)); + + // Amount presets + await page.locator('.pill', { hasText: '$25' }).click(); + await page.waitForTimeout(200); + const presetActive = await page.locator('.pill.active', { hasText: '$25' }).isVisible().catch(() => false); + record('Support: selecting a preset amount highlights it', presetActive); + + // Custom amount overrides preset + await page.locator('.custom-input').fill('17'); + await page.waitForTimeout(200); + const customActive = await page.locator('.pill-custom.active').isVisible().catch(() => false); + record('Support: entering a custom amount switches selection to custom', customActive); + const btnShowsCustom = await page.locator('.donate-btn', { hasText: '$17' }).isVisible().catch(() => false); + record('Support: button reflects the custom amount', btnShowsCustom); + await page.locator('.custom-input').fill(''); + + // Each frequency should open a distinct, real, live Polar checkout page + const frequencies = [ + { label: 'One-Time', btnText: 'Support Now' }, + { label: 'Monthly 🌙', btnText: 'Subscribe Monthly' }, + { label: 'Quarterly', btnText: 'Subscribe Quarterly' }, + { label: 'Yearly', btnText: 'Subscribe Yearly' } + ]; + const openedUrls = new Set(); + for (const freq of frequencies) { + await page.locator('.frequency-toggle button', { hasText: freq.label }).click(); + await page.waitForTimeout(200); + await page.locator('.pill', { hasText: '$10' }).click(); + await page.waitForTimeout(200); + + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.locator('.donate-btn').click() + ]); + await popup.waitForLoadState('domcontentloaded').catch(() => {}); + await page.waitForTimeout(1500); + const finalUrl = popup.url(); + openedUrls.add(finalUrl); + const reachedPolar = /polar\.sh|stripe\.com/i.test(finalUrl); + record(`Support (${freq.label}): opens a real, live checkout page`, reachedPolar, finalUrl); + await popup.close(); + } + record('Support: each frequency opens a genuinely distinct checkout session', openedUrls.size === frequencies.length, `${openedUrls.size} distinct URLs`); + + // Partnership section + await page.locator('a', { hasText: 'View Partnership Opportunities' }).click(); + await page.waitForTimeout(500); + record('Support: partnership section is reachable', await page.locator('h3', { hasText: 'Co-Branding' }).isVisible().catch(() => false)); + const emailLinkVisible = await page.locator('a[href^="mailto:info@falahos.my"]').isVisible().catch(() => false); + record('Support: shows the correct VP sales email contact', emailLinkVisible); + const whatsappLinkVisible = await page.locator('a[href="https://wa.me/60132250691"]').isVisible().catch(() => false); + record('Support: shows the correct WhatsApp contact', whatsappLinkVisible); + record('Support: mentions white-labeling', (await page.locator('.partner-tiers').innerText()).toLowerCase().includes('white-label')); + + record('No uncaught JS console errors during full session', consoleErrors.length === 0, consoleErrors.slice(0, 5).join(' || ')); + + await browser.close(); + const passCount = results.filter(r => r.pass).length; + const failCount = results.length - passCount; + console.log(`\n${passCount} passed, ${failCount} failed, ${results.length} total`); + if (failCount > 0) results.filter(r => !r.pass).forEach(r => console.log(` - ${r.name}: ${r.detail}`)); + process.exit(failCount > 0 ? 1 : 0); +} +main().catch(e => { console.error('SCRIPT ERROR:', e); process.exit(2); }); diff --git a/src/App.svelte b/src/App.svelte index 3481331..d0f8d7a 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -29,6 +29,7 @@ import Locators from './lib/Locators.svelte'; import QuranReader from './lib/QuranReader.svelte'; import NeighbourhoodBoard from './lib/NeighbourhoodBoard.svelte'; + import SupportTab from './lib/SupportTab.svelte'; let currentLang = $state('en'); lang.subscribe(v => currentLang = v); @@ -75,7 +76,8 @@ { key: 'giving', label: 'Giving', icon: '🤲', tabs: [ { label: 'Zakat', icon: '🌙', component: ZakatCalculator }, { label: 'Sadaqah', icon: '🤲', component: SadaqahTracker }, - { label: 'Khairat', icon: '🆘', component: KhairatTracker } + { label: 'Khairat', icon: '🆘', component: KhairatTracker }, + { label: 'Support', icon: '🌱', component: SupportTab } ] }, { key: 'family', label: 'Family', icon: '👪', tabs: [ { label: 'Tree', icon: '🌳', component: FamilyTree }, diff --git a/src/lib/SupportTab.svelte b/src/lib/SupportTab.svelte new file mode 100644 index 0000000..4163f84 --- /dev/null +++ b/src/lib/SupportTab.svelte @@ -0,0 +1,180 @@ + + +
This app is free forever — sadaqah jariyah for all. Your support keeps the servers running, the features growing, and the calculations accurate. Every contribution goes directly to app development.
+🏛️ Represent a bank, eWallet, or institution? { e.preventDefault(); document.getElementById('partners-section')?.scrollIntoView({ behavior: 'smooth' }); }}>View Partnership Opportunities →
+Nur Falah is built to be a trusted estate-planning and Muslim-lifestyle companion. We're open to co-branded partnerships with banks, Takaful/insurance operators, and private institutions — and to white-labeling the entire app under your own brand.
+ +A branded version of Nur Falah for your customers — your logo, your domain, our estate-planning and lifestyle engine underneath.
+License the full app under your own brand end-to-end — reach out for terms and technical details.
+