Files
falah-os-ce/wordpress-plugins/falah-shortcodes/assets/falah-shortcodes.js
T
Antigravity AI dceb30ed8c feat: add falah-souq marketplace plugin and native falah_wallet widget
- [falah-souq] new standalone plugin with [souq_marketplace] shortcode
  Fiverr-style service grid with Islamic luxury design (Emerald/Gold/Cream/Navy)
  Custom post type 'souq_service', category filter bar, card hover states
- [falah_wallet] replace iframe with native balance+transactions widget
  Balance card with loading state, send/receive action buttons
  Recent transactions list, login-gated with smooth reveal
  CSS tokens matching Islamic design system
- Rebuild falah-shortcodes.zip with updated wallet widget
2026-07-06 22:10:47 +08:00

423 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Falah Shortcodes — Client Logic */
(function () {
'use strict';
const cfg = window.FalahSC || {};
// ── Prayer Times ─────────────────────────────────────────────────────────
const prayerWidget = document.querySelector('.falah-prayer-times');
if (prayerWidget) {
const city = prayerWidget.dataset.city || cfg.prayerCity || 'Kuala Lumpur';
const country = prayerWidget.dataset.country || cfg.prayerCountry || 'Malaysia';
const today = new Date();
const dd = String(today.getDate()).padStart(2, '0');
const mm = String(today.getMonth() + 1).padStart(2, '0');
const yyyy = today.getFullYear();
fetch(`https://api.aladhan.com/v1/timingsByCity/${dd}-${mm}-${yyyy}?city=${encodeURIComponent(city)}&country=${encodeURIComponent(country)}&method=3`)
.then(r => r.json())
.then(data => {
if (data.code !== 200) throw new Error('API error');
const t = data.data.timings;
const h = data.data.date.hijri;
const prayers = [
{ key: 'Fajr', label: 'Fajr' },
{ key: 'Sunrise', label: 'Sunrise' },
{ key: 'Dhuhr', label: 'Dhuhr' },
{ key: 'Asr', label: 'Asr' },
{ key: 'Maghrib', label: 'Maghrib' },
{ key: 'Isha', label: 'Isha' },
];
const now = today.getHours() * 60 + today.getMinutes();
const grid = document.getElementById('falah-prayer-grid');
let nextName = '', nextTime = '';
grid.innerHTML = prayers.map(p => {
const raw = t[p.key] || '';
const parts = raw.split(' ')[0].split(':');
const pMin = parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
const isNext = !nextName && pMin > now && p.key !== 'Sunrise';
if (isNext) { nextName = p.label; nextTime = raw.split(' ')[0]; }
return `<div class="falah-prayer-item${isNext ? ' active' : ''}">
<div class="falah-prayer-name">${p.label}</div>
<div class="falah-prayer-time">${raw.split(' ')[0]}</div>
</div>`;
}).join('');
const nextEl = document.getElementById('falah-next-prayer');
if (nextEl && nextName) {
nextEl.textContent = `⏰ Next: ${nextName} at ${nextTime}`;
}
const hijriEl = document.getElementById('falah-hijri');
if (hijriEl) {
hijriEl.textContent = `${h.day} ${h.month.en} ${h.year} AH`;
}
// Also update dashboard
updateDashboardPrayer(nextName, nextTime);
})
.catch(() => {
const grid = document.getElementById('falah-prayer-grid');
if (grid) grid.innerHTML = '<div class="falah-loading"><span>Unable to load prayer times. Check your connection.</span></div>';
});
}
function updateDashboardPrayer(name, time) {
const np = document.getElementById('falah-dash-next-prayer');
const nt = document.getElementById('falah-dash-next-time');
if (np) np.textContent = name || '—';
if (nt) nt.textContent = time ? `at ${time}` : '';
}
// ── Daily Verse ──────────────────────────────────────────────────────────
const verseWidget = document.getElementById('falah-daily-verse');
if (verseWidget) {
const verseNum = parseInt(verseWidget.dataset.verse, 10) || 1;
fetch(`https://api.alquran.cloud/v1/ayah/${verseNum}/editions/quran-uthmani,en.asad`)
.then(r => r.json())
.then(data => {
if (data.code !== 200) throw new Error('API error');
const arabic = data.data[0];
const english = data.data[1];
document.getElementById('falah-verse-loading').style.display = 'none';
const content = document.getElementById('falah-verse-content');
content.style.display = 'block';
document.getElementById('falah-verse-arabic').textContent = arabic.text;
document.getElementById('falah-verse-translation').textContent = `"${english.text}"`;
document.getElementById('falah-verse-ref').textContent = `Surah ${arabic.surah.englishName} (${arabic.surah.number}:${arabic.numberInSurah})`;
// Update dashboard
const dv = document.getElementById('falah-dash-verse');
if (dv) dv.textContent = `${arabic.surah.englishName} ${arabic.surah.number}:${arabic.numberInSurah}`;
})
.catch(() => {
const loading = document.getElementById('falah-verse-loading');
if (loading) loading.innerHTML = '<p style="padding:24px;color:#666">Unable to load verse. Please try again.</p>';
});
}
// ── Dhikr Counter ────────────────────────────────────────────────────────
const dhikrWidget = document.getElementById('falah-dhikr-widget');
if (dhikrWidget) {
let list = [];
try { list = JSON.parse(dhikrWidget.dataset.list || '[]'); } catch (e) {}
const arabicMap = {
'SubhanAllah': 'سُبْحَانَ اللَّهِ',
'Alhamdulillah': 'الْحَمْدُ لِلَّهِ',
'Allahu Akbar': 'اللَّهُ أَكْبَرُ',
'La ilaha illallah': 'لَا إِلَٰهَ إِلَّا اللَّهُ',
'Astaghfirullah': 'أَسْتَغْفِرُ اللَّهَ',
};
const storageKey = 'falah_dhikr_' + new Date().toDateString();
let state = JSON.parse(localStorage.getItem(storageKey) || '{"idx":0,"counts":[0,0,0,0,0],"total":0}');
const ring = document.getElementById('falah-ring');
const numEl = document.getElementById('falah-counter-number');
const targetEl = document.getElementById('falah-counter-target');
const arabicEl = document.getElementById('falah-dhikr-arabic');
const meaningEl = document.getElementById('falah-dhikr-meaning');
const sessionEl = document.getElementById('falah-session-total');
const tapBtn = document.getElementById('falah-btn-tap');
const resetBtn = document.getElementById('falah-btn-reset');
const circumference = 2 * Math.PI * 50; // r=50
function render() {
const d = list[state.idx];
const cnt = state.counts[state.idx] || 0;
const tgt = d ? d.target : 33;
const pct = Math.min(cnt / tgt, 1);
numEl.textContent = cnt;
targetEl.textContent = `/ ${tgt}`;
arabicEl.textContent = (d && arabicMap[d.text]) || '';
meaningEl.textContent = (d && d.meaning) || '';
ring.style.strokeDashoffset = circumference * (1 - pct);
const total = state.counts.reduce((a, b) => a + b, 0);
sessionEl.textContent = total > 0 ? `Session total: ${total} counts` : '';
// Dashboard
const dd = document.getElementById('falah-dash-dhikr-count');
if (dd) dd.textContent = total;
}
function save() {
localStorage.setItem(storageKey, JSON.stringify(state));
}
// Button handlers
document.querySelectorAll('.falah-dhikr-btn').forEach(btn => {
btn.addEventListener('click', () => {
state.idx = parseInt(btn.dataset.index, 10);
document.querySelectorAll('.falah-dhikr-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
save();
render();
});
});
tapBtn.addEventListener('click', () => {
const d = list[state.idx];
const tgt = d ? d.target : 33;
state.counts[state.idx] = (state.counts[state.idx] || 0) + 1;
if (state.counts[state.idx] === tgt) {
setTimeout(() => {
// Flash completion
numEl.style.color = '#1a7a4a';
setTimeout(() => { numEl.style.color = ''; }, 1000);
}, 10);
}
save();
render();
});
resetBtn.addEventListener('click', () => {
state.counts[state.idx] = 0;
save();
render();
});
ring.style.strokeDasharray = circumference;
render();
}
// ── Qibla Compass ────────────────────────────────────────────────────────
const qiblaBtn = document.getElementById('falah-qibla-locate');
if (qiblaBtn) {
qiblaBtn.addEventListener('click', () => {
if (!navigator.geolocation) {
qiblaBtn.textContent = '⚠️ Geolocation not supported';
return;
}
qiblaBtn.textContent = '📍 Getting location…';
qiblaBtn.disabled = true;
navigator.geolocation.getCurrentPosition(pos => {
const lat = pos.coords.latitude;
const lon = pos.coords.longitude;
const qiblaAngle = calcQibla(lat, lon);
document.getElementById('falah-qibla-status').style.display = 'none';
document.getElementById('falah-qibla-display').style.display = 'block';
document.getElementById('falah-qibla-angle').textContent = `Qibla: ${Math.round(qiblaAngle)}° from North`;
document.getElementById('falah-qibla-location').textContent = `Your location: ${lat.toFixed(4)}°, ${lon.toFixed(4)}°`;
const needle = document.getElementById('falah-qibla-needle');
// Try device orientation
if (window.DeviceOrientationEvent && typeof DeviceOrientationEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission().then(perm => {
if (perm === 'granted') listenOrientation(needle, qiblaAngle);
});
} else if (window.DeviceOrientationEvent) {
listenOrientation(needle, qiblaAngle);
} else {
needle.style.transform = `rotate(${qiblaAngle}deg)`;
}
}, () => {
qiblaBtn.textContent = '⚠️ Location denied. Enable location access and try again.';
qiblaBtn.disabled = false;
});
});
}
function listenOrientation(needle, qiblaAngle) {
window.addEventListener('deviceorientationabsolute', e => {
const heading = e.alpha ? (360 - e.alpha) : 0;
needle.style.transform = `rotate(${qiblaAngle - heading}deg)`;
}, true);
}
function calcQibla(lat, lon) {
// Kaaba coordinates
const kLat = 21.3891;
const kLon = 39.8579;
const φ1 = lat * Math.PI / 180;
const φ2 = kLat * Math.PI / 180;
const Δλ = (kLon - lon) * Math.PI / 180;
const y = Math.sin(Δλ) * Math.cos(φ2);
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
const θ = Math.atan2(y, x);
return ((θ * 180 / Math.PI) + 360) % 360;
}
// ── Dashboard ─────────────────────────────────────────────────────────────
const dashDate = document.getElementById('falah-dash-date');
if (dashDate) {
const opts = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
dashDate.textContent = new Date().toLocaleDateString('en-GB', opts);
}
// Hijri date on dashboard
fetch('https://api.aladhan.com/v1/gToH?date=' + new Date().toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' }))
.then(r => r.json())
.then(data => {
const h = data?.data?.hijri;
if (!h) return;
const dashHijri = document.getElementById('falah-dash-hijri');
if (dashHijri) dashHijri.textContent = `${h.day} ${h.month.en} ${h.year} AH`;
const hijriEl = document.getElementById('falah-hijri');
if (hijriEl) hijriEl.textContent = `${h.day} ${h.month.en} ${h.year} AH`;
})
.catch(() => {});
// ── Falah Wallet ──────────────────────────────────────────────────────────
const walletWidget = document.getElementById('falah-wallet-widget');
if (walletWidget) {
const balanceEl = document.getElementById('falah-wallet-balance');
const balanceFiat = document.getElementById('falah-wallet-balance-fiat');
const btnSend = document.getElementById('falah-wallet-btn-send');
const btnReceive = document.getElementById('falah-wallet-btn-receive');
const txList = document.getElementById('falah-wallet-tx-list');
const loginPrompt = document.getElementById('falah-wallet-login-prompt');
const sendModal = document.getElementById('falah-wallet-send-modal');
const receiveModal = document.getElementById('falah-wallet-receive-modal');
function openModal(el) { el.style.display = 'flex'; document.body.style.overflow = 'hidden'; }
function closeModal(el) { el.style.display = 'none'; document.body.style.overflow = ''; }
if (btnSend) btnSend.addEventListener('click', () => sendModal && openModal(sendModal));
if (btnReceive) btnReceive.addEventListener('click', () => receiveModal && openModal(receiveModal));
document.getElementById('falah-wallet-send-backdrop')?.addEventListener('click', () => closeModal(sendModal));
document.getElementById('falah-wallet-send-close')?.addEventListener('click', () => closeModal(sendModal));
document.getElementById('falah-wallet-receive-backdrop')?.addEventListener('click', () => closeModal(receiveModal));
document.getElementById('falah-wallet-receive-close')?.addEventListener('click', () => closeModal(receiveModal));
// Copy address
document.getElementById('falah-wallet-copy-addr')?.addEventListener('click', function () {
const addr = document.getElementById('falah-wallet-address-text')?.textContent || '';
if (!addr) return;
navigator.clipboard?.writeText(addr).then(() => {
this.title = 'Copied!';
setTimeout(() => { this.title = 'Copy address'; }, 1500);
});
});
// Send form
document.getElementById('falah-wallet-send-submit')?.addEventListener('click', function () {
const to = document.getElementById('falah-wallet-send-to')?.value.trim();
const amount = parseFloat(document.getElementById('falah-wallet-send-amount')?.value);
const errEl = document.getElementById('falah-wallet-send-err');
if (!to || !to.startsWith('0x') || to.length < 10) {
if (errEl) errEl.textContent = 'Please enter a valid recipient address.';
return;
}
if (!amount || amount <= 0) {
if (errEl) errEl.textContent = 'Please enter a valid amount.';
return;
}
if (errEl) errEl.textContent = '';
// Redirect to wallet app with prefilled params
const base = (cfg.mobileUrl || 'https://falahos.my/mobile').replace(/\/$/, '');
const url = `${base}/wallet/send?to=${encodeURIComponent(to)}&amount=${encodeURIComponent(amount)}`;
window.open(url, '_blank', 'noopener,noreferrer');
closeModal(sendModal);
});
// Fetch wallet data from Falah Mobile REST API
function fetchWalletData() {
const base = (cfg.mobileUrl || 'https://falahos.my/mobile').replace(/\/$/, '');
fetch(`${base}/api/wallet/balance`, {
credentials: 'include',
headers: { 'X-WP-Nonce': cfg.nonce || '' },
})
.then(r => {
if (r.status === 401 || r.status === 403) throw new Error('unauthenticated');
if (!r.ok) throw new Error('api_error');
return r.json();
})
.then(data => {
if (balanceEl) balanceEl.textContent = (data.balance || '0') + ' ' + (data.currency || 'FLH');
if (balanceFiat && data.fiat_value) balanceFiat.textContent = '≈ ' + data.fiat_value;
if (btnSend) btnSend.disabled = false;
if (btnReceive) btnReceive.disabled = false;
// Render receive address + QR
if (data.address) {
const addrEl = document.getElementById('falah-wallet-address-text');
if (addrEl) addrEl.textContent = data.address;
const qrInner = document.getElementById('falah-wallet-qr-inner');
if (qrInner) {
const img = document.createElement('img');
img.alt = 'Wallet QR Code';
img.src = `https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(data.address)}&size=160x160&margin=4`;
img.width = 160; img.height = 160;
qrInner.innerHTML = '';
qrInner.appendChild(img);
}
}
return fetch(`${base}/api/wallet/transactions?limit=5`, {
credentials: 'include',
headers: { 'X-WP-Nonce': cfg.nonce || '' },
});
})
.then(r => r && r.ok ? r.json() : null)
.then(data => {
if (!txList || !data) return;
const items = Array.isArray(data.transactions) ? data.transactions : [];
txList.innerHTML = '';
if (!items.length) {
txList.innerHTML = '<div class="falah-wallet-tx-empty">No transactions yet</div>';
return;
}
items.forEach(tx => {
const type = tx.type === 'sent' ? 'sent' : 'received';
const icon = type === 'sent' ? '↑' : '↓';
const sign = type === 'sent' ? '' : '+';
const date = tx.date ? new Date(tx.date).toLocaleDateString('en-GB', { day: 'numeric', month: 'short' }) : '';
const desc = tx.description || (type === 'sent' ? 'Sent' : 'Received');
const amount = (tx.amount || '0') + ' ' + (tx.currency || 'FLH');
const row = document.createElement('div');
row.className = 'falah-wallet-tx-item';
row.innerHTML = `
<div class="falah-wallet-tx-icon ${type}">${icon}</div>
<div class="falah-wallet-tx-info">
<div class="falah-wallet-tx-desc">${escapeHtml(desc)}</div>
<div class="falah-wallet-tx-date">${escapeHtml(date)}</div>
</div>
<div class="falah-wallet-tx-amount ${type}">${sign}${escapeHtml(amount)}</div>
`;
txList.appendChild(row);
});
})
.catch(err => {
if (err.message === 'unauthenticated') {
if (balanceEl) balanceEl.textContent = '—';
if (loginPrompt) loginPrompt.style.display = '';
if (txList) txList.innerHTML = '';
} else {
if (balanceEl) balanceEl.textContent = '—';
if (txList) txList.innerHTML = '<div class="falah-wallet-tx-empty">Could not load wallet. <a href="' + (cfg.mobileUrl || '') + '/wallet" target="_blank" rel="noopener">Open wallet app →</a></div>';
}
});
}
fetchWalletData();
}
function escapeHtml(str) {
return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
})();