diff --git a/configs/nginx-falah-mobile.conf b/configs/nginx-falah-mobile.conf new file mode 100644 index 0000000..b7f8d65 --- /dev/null +++ b/configs/nginx-falah-mobile.conf @@ -0,0 +1,18 @@ +# Nginx reverse proxy config for Falah Mobile +# Add this inside the server {} block for falahos.my + +location /mobile { + proxy_pass http://127.0.0.1:4013; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + + # Allow iframes from ummah.falahos.my + add_header X-Frame-Options "ALLOW-FROM https://ummah.falahos.my"; + add_header Content-Security-Policy "frame-ancestors 'self' https://ummah.falahos.my"; +} diff --git a/deploy-falah-mobile.sh b/deploy-falah-mobile.sh new file mode 100644 index 0000000..ef06191 --- /dev/null +++ b/deploy-falah-mobile.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# deploy-falah-mobile.sh — Build and deploy FalahMobile Next.js app to production +# Usage: bash deploy-falah-mobile.sh [ssh-user] +# Prerequisites: SSH key access to server, Docker installed on server +# +# Credentials: retrieve from bitwarden.falahos.my +# - Server SSH: "Falah OS CE Server - 13.140.161.244" + +set -euo pipefail + +SERVER="${1:-13.140.161.244}" +SSH_USER="${2:-root}" +REMOTE="$SSH_USER@$SERVER" +DEPLOY_DIR="/opt/falah-mobile" +APP_PORT="4013" +IMAGE="falah-mobile:latest" +CONTAINER="falah-mobile" + +echo "▶ Deploying Falah Mobile to $SERVER:$APP_PORT" + +# ── 1. Transfer source to server ───────────────────────────────────────────── +echo "── Syncing source files ──" +ssh "$REMOTE" "mkdir -p $DEPLOY_DIR" + +rsync -az --delete \ + --exclude='.git' \ + --exclude='node_modules' \ + --exclude='.next' \ + --exclude='*.log' \ + "$(dirname "$0")/../FalahMobile/" \ + "$REMOTE:$DEPLOY_DIR/" + +# ── 2. Build and run on server ─────────────────────────────────────────────── +ssh "$REMOTE" bash -s << REMOTE_SCRIPT +set -euo pipefail + +cd "$DEPLOY_DIR" + +# Create .env if missing +if [ ! -f .env ]; then + cat > .env << 'ENVEOF' +NODE_ENV=production +PORT=3000 +DATABASE_URL=file:./data/falah.db +NEXTAUTH_URL=https://falahos.my/mobile +NEXTAUTH_SECRET=$(openssl rand -base64 32) +ENVEOF + echo "⚠️ Created .env — review and update NEXTAUTH_URL and secrets" +fi + +echo "── Building Docker image ──" +docker build -t $IMAGE . + +echo "── Stopping old container (if any) ──" +docker rm -f $CONTAINER 2>/dev/null || true + +echo "── Starting new container ──" +docker run -d \ + --name $CONTAINER \ + --restart unless-stopped \ + -p $APP_PORT:3000 \ + -v $DEPLOY_DIR/data:/app/data \ + --env-file .env \ + $IMAGE + +echo "── Running Prisma migrations ──" +sleep 5 +docker exec $CONTAINER npx prisma migrate deploy --schema=./prisma/schema.prisma || true + +echo "── Health check ──" +sleep 5 +if curl -sf "http://localhost:$APP_PORT" > /dev/null 2>&1; then + echo "✅ Falah Mobile is running at http://$SERVER:$APP_PORT" +else + echo "⚠️ Health check failed — checking logs:" + docker logs --tail 30 $CONTAINER +fi +REMOTE_SCRIPT + +echo "" +echo "✅ Deploy complete." +echo " App: http://$SERVER:$APP_PORT" +echo " Nur AI: http://$SERVER:$APP_PORT/nur" +echo " Wallet: http://$SERVER:$APP_PORT/wallet" +echo " Souq: http://$SERVER:$APP_PORT/souq" +echo "" +echo "Next: Configure Cloudflare/Nginx to proxy falahos.my/mobile → $SERVER:$APP_PORT" +echo " Then update WordPress Settings → Falah Shortcodes → Mobile URL to: https://falahos.my/mobile" diff --git a/wordpress-plugins/falah-shortcodes/README.md b/wordpress-plugins/falah-shortcodes/README.md new file mode 100644 index 0000000..ca90fe8 --- /dev/null +++ b/wordpress-plugins/falah-shortcodes/README.md @@ -0,0 +1,53 @@ +# Falah Shortcodes — WordPress Plugin + +Implements all missing shortcodes for ummah.falahos.my. + +## Installation + +1. Upload the `falah-shortcodes/` folder to `/wp-content/plugins/` +2. Activate in **Plugins → Installed Plugins** +3. Configure in **Settings → Falah Shortcodes**: + - Set **Falah Mobile URL** (default: `https://falahos.my/mobile`) + - Set default prayer city/country + +## Shortcodes + +| Shortcode | Page | Notes | +|-----------|------|-------| +| `[prayer_times]` | /tools | Calls Aladhan API — no key needed | +| `[dhikr_counter]` | /tools | localStorage — works offline | +| `[qibla_compass]` | /tools | Requires browser geolocation permission | +| `[daily_verse]` | /tools | Calls Al-Quran Cloud API — no key needed | +| `[nur_ai_chat]` | /nur | Iframes Falah Mobile `/nur` | +| `[falah_wallet]` | /wallet | Iframes Falah Mobile `/wallet` | +| `[souq_marketplace]` | /souq | Iframes Falah Mobile `/souq` | +| `[falah_dashboard]` | /dashboard | Auto-injected if dashboard page is empty | + +## Deploying Falah Mobile (required for nur/wallet/souq) + +Run from the falah-os-ce directory: + +```bash +bash deploy-falah-mobile.sh 13.140.161.244 root +``` + +Then add this to the Nginx vhost for falahos.my: + +```nginx +location /mobile { + proxy_pass http://127.0.0.1:4013; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} +``` + +Update **Settings → Falah Shortcodes → Falah Mobile URL** to `https://falahos.my/mobile`. + +## What each standalone feature uses + +- **Prayer Times** — [Aladhan API](https://aladhan.com/prayer-times-api) (free, no auth) +- **Daily Verse** — [AlQuran.cloud API](https://alquran.cloud/api) (free, no auth) +- **Dhikr Counter** — browser localStorage (no network call) +- **Qibla Compass** — browser Geolocation + DeviceOrientation APIs (no network call) diff --git a/wordpress-plugins/falah-shortcodes/assets/falah-shortcodes.css b/wordpress-plugins/falah-shortcodes/assets/falah-shortcodes.css new file mode 100644 index 0000000..ca9d9c2 --- /dev/null +++ b/wordpress-plugins/falah-shortcodes/assets/falah-shortcodes.css @@ -0,0 +1,253 @@ +/* Falah Shortcodes — Design System */ +:root { + --falah-green: #1a7a4a; + --falah-green-light: #2da065; + --falah-gold: #c9a84c; + --falah-gold-light: #e6c97a; + --falah-surface: #f8f6f0; + --falah-surface-dark: #1c1f2e; + --falah-text: #1a1a2e; + --falah-text-muted: #5a6070; + --falah-border: rgba(0,0,0,0.08); + --falah-radius: 16px; + --falah-shadow: 0 4px 24px rgba(0,0,0,0.08); + --falah-shadow-lg: 0 8px 40px rgba(0,0,0,0.12); + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); +} + +/* ── Base widget ──────────────────────────────────────────────────────────── */ +.falah-widget { + background: #fff; + border-radius: var(--falah-radius); + box-shadow: var(--falah-shadow); + overflow: hidden; + margin: 24px 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + color: var(--falah-text); +} + +.falah-widget-header { + display: flex; + align-items: center; + gap: 10px; + padding: 20px 24px 16px; + border-bottom: 1px solid var(--falah-border); + background: linear-gradient(135deg, var(--falah-green) 0%, #145c38 100%); + color: #fff; +} +.falah-widget-header h3 { + margin: 0; + font-size: 1.1rem; + font-weight: 600; + color: #fff; + flex: 1; +} +.falah-widget-header .falah-location { + font-size: 0.8rem; + opacity: 0.8; +} +.falah-icon { font-size: 1.4rem; } + +/* ── Spinner ──────────────────────────────────────────────────────────────── */ +.falah-spinner { + width: 32px; height: 32px; + border: 3px solid var(--falah-border); + border-top-color: var(--falah-green); + border-radius: 50%; + animation: falah-spin 0.7s linear infinite; +} +@keyframes falah-spin { to { transform: rotate(360deg); } } +.falah-loading { + display: flex; align-items: center; gap: 12px; + justify-content: center; padding: 32px; + color: var(--falah-text-muted); font-size: 0.9rem; +} + +/* ── Prayer Times ─────────────────────────────────────────────────────────── */ +.falah-prayer-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); + gap: 1px; + background: var(--falah-border); +} +.falah-prayer-item { + background: #fff; + padding: 16px 12px; + text-align: center; + transition: background 200ms; +} +.falah-prayer-item.active { + background: linear-gradient(135deg, #e8f5ee, #f0faf4); +} +.falah-prayer-item.active .falah-prayer-name { color: var(--falah-green); font-weight: 700; } +.falah-prayer-name { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--falah-text-muted); margin-bottom: 6px; } +.falah-prayer-time { font-size: 1.15rem; font-weight: 600; font-variant-numeric: tabular-nums; } +.falah-prayer-item.active .falah-prayer-time { color: var(--falah-green); } +.falah-next-prayer { + padding: 12px 24px; + font-size: 0.85rem; + color: var(--falah-green); + font-weight: 500; + text-align: center; +} +.falah-hijri { + padding: 0 24px 16px; + font-size: 0.8rem; + color: var(--falah-text-muted); + text-align: center; +} + +/* ── Dhikr Counter ────────────────────────────────────────────────────────── */ +.falah-dhikr-selector { + display: flex; flex-wrap: wrap; gap: 8px; + padding: 16px 20px; +} +.falah-dhikr-btn { + padding: 6px 14px; border-radius: 20px; + border: 1.5px solid var(--falah-border); + background: var(--falah-surface); cursor: pointer; + font-size: 0.82rem; color: var(--falah-text-muted); + transition: all 150ms; +} +.falah-dhikr-btn.active, .falah-dhikr-btn:hover { + background: var(--falah-green); color: #fff; border-color: var(--falah-green); +} +.falah-dhikr-display { text-align: center; padding: 8px 20px 4px; } +.falah-dhikr-arabic { font-size: 2rem; font-family: 'Scheherazade New', 'KFGQPC Uthmanic Script HAFS', serif; direction: rtl; line-height: 1.6; color: var(--falah-text); } +.falah-dhikr-meaning { font-size: 0.85rem; color: var(--falah-text-muted); margin-top: 4px; } +.falah-counter-circle { + position: relative; width: 140px; height: 140px; + margin: 16px auto 4px; +} +.falah-counter-circle svg { transform: rotate(-90deg); } +.falah-ring-bg { fill: none; stroke: var(--falah-border); stroke-width: 8; } +.falah-ring-progress { + fill: none; stroke: var(--falah-green); stroke-width: 8; + stroke-linecap: round; + stroke-dasharray: 314; + stroke-dashoffset: 314; + transition: stroke-dashoffset 300ms var(--ease-out-expo); +} +.falah-counter-number { + position: absolute; inset: 0; + display: flex; align-items: center; justify-content: center; + font-size: 2.2rem; font-weight: 700; color: var(--falah-text); + font-variant-numeric: tabular-nums; +} +.falah-counter-target { text-align: center; color: var(--falah-text-muted); font-size: 0.9rem; margin-bottom: 16px; } +.falah-counter-actions { display: flex; gap: 12px; justify-content: center; padding: 0 20px 16px; } +.falah-btn-tap { + flex: 1; max-width: 160px; + padding: 14px 24px; + background: var(--falah-green); color: #fff; + border: none; border-radius: 12px; cursor: pointer; + font-size: 1rem; font-weight: 600; + box-shadow: 0 4px 14px rgba(26,122,74,0.3); + transition: transform 80ms, box-shadow 150ms; + user-select: none; -webkit-tap-highlight-color: transparent; +} +.falah-btn-tap:active { transform: scale(0.95); box-shadow: none; } +.falah-btn-reset { + padding: 14px 20px; background: var(--falah-surface); + color: var(--falah-text-muted); border: 1.5px solid var(--falah-border); + border-radius: 12px; cursor: pointer; font-size: 0.9rem; + transition: background 150ms; +} +.falah-btn-reset:hover { background: #fee2e2; color: #dc2626; border-color: #fecaca; } +.falah-session-total { text-align: center; padding: 0 20px 16px; font-size: 0.8rem; color: var(--falah-text-muted); min-height: 20px; } + +/* ── Qibla Compass ────────────────────────────────────────────────────────── */ +.falah-qibla-status { padding: 32px; text-align: center; } +.falah-btn-locate { + padding: 12px 28px; background: var(--falah-green); color: #fff; + border: none; border-radius: 12px; cursor: pointer; font-size: 1rem; + box-shadow: 0 4px 14px rgba(26,122,74,0.3); transition: transform 80ms; +} +.falah-btn-locate:active { transform: scale(0.97); } +.falah-qibla-display { padding: 20px; } +.falah-compass-wrap { display: flex; justify-content: center; margin-bottom: 16px; } +.falah-compass { + position: relative; width: 180px; height: 180px; + border-radius: 50%; background: radial-gradient(circle at 40% 35%, #f0f4ff, #e8ecf8); + box-shadow: 0 0 0 4px var(--falah-border), var(--falah-shadow-lg); + display: flex; align-items: center; justify-content: center; +} +.falah-compass-rose { position: absolute; inset: 0; } +.falah-dir { + position: absolute; font-size: 0.75rem; font-weight: 700; color: var(--falah-text-muted); +} +.falah-dir.n { top: 8px; left: 50%; transform: translateX(-50%); } +.falah-dir.s { bottom: 8px; left: 50%; transform: translateX(-50%); } +.falah-dir.e { right: 10px; top: 50%; transform: translateY(-50%); } +.falah-dir.w { left: 10px; top: 50%; transform: translateY(-50%); } +.falah-needle-kaaba { + font-size: 2rem; transition: transform 0.3s ease-out; + transform-origin: center; +} +.falah-qibla-info { text-align: center; color: var(--falah-text-muted); font-size: 0.9rem; } +#falah-qibla-angle { font-size: 1.2rem; font-weight: 600; color: var(--falah-green); margin-bottom: 4px; } + +/* ── Daily Verse ──────────────────────────────────────────────────────────── */ +.falah-verse-loading { display: flex; justify-content: center; padding: 32px; } +.falah-verse-content { padding: 24px; } +.falah-verse-arabic { + font-family: 'Scheherazade New', 'KFGQPC Uthmanic Script HAFS', serif; + font-size: 1.5rem; line-height: 2; text-align: right; + color: var(--falah-text); margin-bottom: 16px; + padding-bottom: 16px; border-bottom: 1px solid var(--falah-border); +} +.falah-verse-translation { font-size: 1rem; line-height: 1.7; color: var(--falah-text); font-style: italic; } +.falah-verse-ref { margin-top: 12px; font-size: 0.8rem; color: var(--falah-green); font-weight: 600; } + +/* ── Iframe Widget ────────────────────────────────────────────────────────── */ +.falah-iframe-container { position: relative; height: 600px; } +.falah-iframe { + width: 100%; height: 100%; border: none; display: block; + position: relative; z-index: 2; +} +.falah-iframe-fallback { + position: absolute; inset: 0; z-index: 1; + display: flex; flex-direction: column; align-items: center; justify-content: center; + gap: 12px; background: var(--falah-surface); color: var(--falah-text-muted); +} +.falah-open-link { + color: var(--falah-green); text-decoration: none; font-weight: 500; font-size: 0.9rem; +} + +/* ── Dashboard ────────────────────────────────────────────────────────────── */ +.falah-dashboard { padding: 4px 0; } +.falah-dashboard-header { margin-bottom: 24px; } +.falah-dashboard-header h2 { font-size: 1.6rem; font-weight: 700; margin: 0 0 4px; } +.falah-dashboard-date { color: var(--falah-text-muted); margin: 0; font-size: 0.9rem; } +.falah-dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; margin-bottom: 28px; +} +.falah-dash-card { + background: #fff; border-radius: var(--falah-radius); + box-shadow: var(--falah-shadow); padding: 20px; + display: flex; gap: 16px; align-items: flex-start; + border-left: 4px solid var(--falah-green); +} +.falah-dash-card-icon { font-size: 1.8rem; flex-shrink: 0; } +.falah-dash-card-label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--falah-text-muted); margin-bottom: 6px; } +.falah-dash-card-value { font-size: 1.1rem; font-weight: 600; line-height: 1.3; } +.falah-dash-card-sub { font-size: 0.8rem; color: var(--falah-text-muted); margin-top: 2px; } +.falah-dashboard-links { + display: flex; flex-wrap: wrap; gap: 12px; +} +.falah-dash-link { + padding: 10px 18px; background: #fff; border-radius: 10px; + box-shadow: var(--falah-shadow); text-decoration: none; + color: var(--falah-text); font-size: 0.9rem; font-weight: 500; + transition: box-shadow 150ms, transform 150ms; + border: 1px solid var(--falah-border); +} +.falah-dash-link:hover { box-shadow: var(--falah-shadow-lg); transform: translateY(-2px); color: var(--falah-green); } + +@media (max-width: 600px) { + .falah-prayer-grid { grid-template-columns: repeat(3, 1fr); } + .falah-dashboard-grid { grid-template-columns: 1fr 1fr; } + .falah-iframe-container { height: 500px; } +} diff --git a/wordpress-plugins/falah-shortcodes/assets/falah-shortcodes.js b/wordpress-plugins/falah-shortcodes/assets/falah-shortcodes.js new file mode 100644 index 0000000..79000a7 --- /dev/null +++ b/wordpress-plugins/falah-shortcodes/assets/falah-shortcodes.js @@ -0,0 +1,276 @@ +/* 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 `
+
${p.label}
+
${raw.split(' ')[0]}
+
`; + }).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 = '
Unable to load prayer times. Check your connection.
'; + }); + } + + 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 = '

Unable to load verse. Please try again.

'; + }); + } + + // ── 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(() => {}); + +})(); diff --git a/wordpress-plugins/falah-shortcodes/falah-shortcodes.php b/wordpress-plugins/falah-shortcodes/falah-shortcodes.php new file mode 100644 index 0000000..0e1fbca --- /dev/null +++ b/wordpress-plugins/falah-shortcodes/falah-shortcodes.php @@ -0,0 +1,393 @@ + 'string', + 'sanitize_callback' => 'esc_url_raw', + 'default' => 'https://falahos.my/mobile', + ] ); + register_setting( 'falah_sc_options', 'falah_prayer_city', [ + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => 'Kuala Lumpur', + ] ); + register_setting( 'falah_sc_options', 'falah_prayer_country', [ + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => 'Malaysia', + ] ); +} ); + +function falah_sc_settings_page() { + ?> +
+

Falah Shortcodes Settings

+
+ + + + + + + + + + + + + + +
Falah Mobile URL + +

Base URL for Falah Mobile app (used by [nur_ai_chat], [falah_wallet], [souq_marketplace])

+
Default Prayer City
Default Prayer Country
+ +
+
+ admin_url( 'admin-ajax.php' ), + 'mobileUrl' => esc_url( get_option( 'falah_mobile_url', 'https://falahos.my/mobile' ) ), + 'prayerCity' => esc_js( get_option( 'falah_prayer_city', 'Kuala Lumpur' ) ), + 'prayerCountry' => esc_js( get_option( 'falah_prayer_country', 'Malaysia' ) ), + 'nonce' => wp_create_nonce( 'falah_sc_nonce' ), + ] ); +} ); + +function falah_sc_enqueue() { + wp_enqueue_style( 'falah-sc-styles' ); + wp_enqueue_script( 'falah-sc-scripts' ); +} + +// ─── [prayer_times] ─────────────────────────────────────────────────────────── + +add_shortcode( 'prayer_times', function ( $atts ) { + falah_sc_enqueue(); + $atts = shortcode_atts( [ + 'city' => get_option( 'falah_prayer_city', 'Kuala Lumpur' ), + 'country' => get_option( 'falah_prayer_country', 'Malaysia' ), + ], $atts ); + + $city = esc_attr( $atts['city'] ); + $country = esc_attr( $atts['country'] ); + + ob_start(); + ?> +
+
+ 🕌 +

Prayer Times

+ , +
+
+
+
+ Fetching prayer times… +
+
+
+
+
+ 33, + 'dhikr' => 'SubhanAllah', + ], $atts ); + + $target = intval( $atts['target'] ); + $dhikr = esc_html( $atts['dhikr'] ); + + $dhikr_list = [ + [ 'text' => 'SubhanAllah', 'meaning' => 'Glory be to Allah', 'target' => 33 ], + [ 'text' => 'Alhamdulillah', 'meaning' => 'Praise be to Allah', 'target' => 33 ], + [ 'text' => 'Allahu Akbar', 'meaning' => 'Allah is the Greatest', 'target' => 34 ], + [ 'text' => 'La ilaha illallah', 'meaning' => 'There is no god but Allah', 'target' => 100 ], + [ 'text' => 'Astaghfirullah', 'meaning' => 'I seek forgiveness from Allah', 'target' => 100 ], + ]; + + $list_json = json_encode( $dhikr_list ); + + ob_start(); + ?> +
+
+ 📿 +

Dhikr Counter

+
+
+ $d ) : ?> + + +
+
+
سُبْحَانَ اللَّهِ
+
Glory be to Allah
+
+
+ + + + +
0
+
+
/ 33
+
+ + +
+
+
+ +
+
+ 🧭 +

Qibla Compass

+
+
+ +
+ +
+ 0, 'ayah' => 0 ], $atts ); + + // Deterministic daily verse: use day-of-year as seed (6236 ayahs total) + $day_seed = intval( date( 'z' ) ) + intval( date( 'Y' ) ) * 365; + $verse_num = ( $day_seed % 6236 ) + 1; + + $cache_key = 'falah_verse_' . $verse_num; + $cached = get_transient( $cache_key ); + + if ( $cached !== false ) { + return $cached; + } + + ob_start(); + ?> +
+
+ 📖 +

Verse of the Day

+
+
+
+
+ +
+ +
+
+ +

+
+
+ + +
+
+ +
+
+

Welcome back 👋

+

+
+
+
+
🕌
+
+
Next Prayer
+
Loading…
+
+
+
+
+
📖
+
+
Today's Verse
+
Loading…
+
+
+
+
📿
+
+
Daily Dhikr
+
0
+
counts today
+
+
+
+
🗓️
+
+
Hijri Date
+
Loading…
+
+
+
+ +
+ post_name ?? ''; + if ( $slug === 'dashboard' && trim( strip_tags( $content ) ) === '' ) { + return do_shortcode( '[falah_dashboard]' ); + } + } + return $content; +} );