Add Prayer Reminder, Dhikr Counter, Qibla Finder, Halal Monitor updates

- Prayer reminder: /prayer page with 5 daily times, countdown, alAdhan API
- Dhikr counter: /dhikr page with 3 presets, custom counter, haptics, DB tracking
- Qibla finder: /qibla page with compass SVG, DeviceOrientation, bearing to Kaaba
- Halal Monitor: Leaflet + Google Places, 26 KL markers, geolocation, distance sort
- BottomNav: added clock icon for prayer
- Prisma: schema updates for DhikrSession, DhikrDay, DailyStreak, XpTransaction
- Geo utility module
- Weighted Traefik routing: Contabo 99% / Synology 1% cold standby
This commit is contained in:
root
2026-06-18 14:51:07 +02:00
parent ed34b186f9
commit 28be776c84
13 changed files with 2822 additions and 104 deletions
+61 -1
View File
@@ -8,7 +8,7 @@ import {
Bot, ShoppingBag, MessageCircle, Wallet,
Flame, Sparkles, Star, ChevronRight,
BookOpen, MapPin, Crown, Zap, TrendingUp,
Gift, Trophy,
Gift, Trophy, CircleDot, Compass,
} from "lucide-react";
import { DAILY_VERSE, generateAIResponse } from "@/lib/ai";
import PremiumBadge from "@/components/PremiumBadge";
@@ -19,6 +19,7 @@ export default function HomePage() {
const [verse] = useState(DAILY_VERSE[Math.floor(Math.random() * DAILY_VERSE.length)]);
const [claiming, setClaiming] = useState(false);
const [claimResult, setClaimResult] = useState<string | null>(null);
const [dhikrTodayCount, setDhikrTodayCount] = useState(0);
useEffect(() => {
if (!loading && !token) router.push("/auth");
@@ -34,6 +35,19 @@ export default function HomePage() {
return () => clearInterval(interval);
}, [token, refreshStreak, refreshXp]);
// Fetch dhikr stats
useEffect(() => {
if (!token) return;
fetch("/mobile/api/dhikr/stats", {
headers: { Authorization: `Bearer ${token}` },
})
.then((r) => r.ok ? r.json() : null)
.then((data) => {
if (data?.dayStats?.totalCount != null) setDhikrTodayCount(data.dayStats.totalCount);
})
.catch(() => {});
}, [token]);
const handleClaimReward = async () => {
if (!token || claiming) return;
setClaiming(true);
@@ -245,6 +259,52 @@ export default function HomePage() {
</div>
</div>
{/* Dhikr Today */}
<div className="mx-4 mb-5">
<Link href="/dhikr" className="block bg-gradient-to-br from-emerald-900/15 via-[#111118] to-gray-900/80 border border-emerald-800/25 rounded-2xl p-5 active:scale-[0.98] transition-all">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-9 h-9 rounded-xl bg-emerald-900/30 flex items-center justify-center">
<CircleDot size={18} className="text-emerald-400" />
</div>
<div>
<h3 className="text-sm font-semibold text-white">Dhikr Today</h3>
<p className="text-xs text-gray-500">Digital Tasbih Counter</p>
</div>
</div>
<div className="text-right">
<p className="text-lg font-bold text-emerald-400 tabular-nums">{dhikrTodayCount.toLocaleString()}</p>
<p className="text-[10px] text-gray-600">counts</p>
</div>
</div>
<div className="flex items-center justify-center gap-3 text-xs text-gray-500">
<span className="text-emerald-400/70">سُبْحَانَ ٱللَّٰهِ</span>
<span className="text-gray-600">·</span>
<span className="text-amber-400/70">ٱلْحَمْدُ لِلَّٰهِ</span>
<span className="text-gray-600">·</span>
<span className="text-sky-400/70">ٱللَّٰهُ أَكْبَرُ</span>
</div>
</Link>
</div>
{/* Qibla Finder */}
<div className="mx-4 mb-5">
<Link href="/qibla" className="block bg-gradient-to-br from-amber-900/15 via-[#111118] to-gray-900/80 border border-amber-800/25 rounded-2xl p-5 active:scale-[0.98] transition-all">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-9 h-9 rounded-xl bg-amber-900/30 flex items-center justify-center">
<Compass size={18} className="text-amber-400" />
</div>
<div>
<h3 className="text-sm font-semibold text-white">Qibla Finder</h3>
<p className="text-xs text-gray-500">Find direction to the Kaaba</p>
</div>
</div>
<ChevronRight size={16} className="text-amber-600" />
</div>
</Link>
</div>
{/* Stats Row */}
<div className="mx-4 mb-5">
<div className="flex gap-3">