Redesign all pages to light theme (Qalby-inspired mobile UI)
Complete UI overhaul replacing dark gold theme with a clean mobile-first light theme: gradient emerald headers, white card components, light backgrounds, emerald/amber CTAs, and a new home dashboard with feature grid navigation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+51
@@ -0,0 +1,51 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
.opencode/memories.md
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
.next/
|
||||||
|
.netlify/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Secrets (DO NOT COMMIT)
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
*.p12
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.bak
|
||||||
|
*.sql
|
||||||
|
!infrastructure/postgres/init.sql
|
||||||
|
|
||||||
|
# Claude (workspace config, not project code)
|
||||||
|
.claude/*
|
||||||
|
!.claude/commands/
|
||||||
|
|
||||||
|
# Netlify
|
||||||
|
.functions/
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
||||||
|
export default eslintConfig;
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export default config
|
||||||
+119
-94
@@ -109,14 +109,14 @@ export default function DhikrPage() {
|
|||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh flex flex-col items-center justify-center bg-[#0a0a0f] px-6 pb-20">
|
<div className="min-h-dvh flex flex-col items-center justify-center bg-[#f5f4f0] px-6 pb-20">
|
||||||
<div className="text-5xl mb-6">📿</div>
|
<div className="text-5xl mb-6">📿</div>
|
||||||
<h2 className="text-xl font-bold text-white mb-2">Daily Dhikr</h2>
|
<h2 className="text-xl font-bold text-gray-900 mb-2">Daily Dhikr</h2>
|
||||||
<p className="text-gray-500 text-sm text-center mb-8">
|
<p className="text-gray-500 text-sm text-center mb-8">
|
||||||
Build a daily habit of remembrance. Sign in to track your streak.
|
Build a daily habit of remembrance. Sign in to track your streak.
|
||||||
</p>
|
</p>
|
||||||
<button onClick={() => router.push('/login')}
|
<button onClick={() => router.push('/login')}
|
||||||
className="w-full max-w-xs bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold active:scale-[0.98] transition-all">
|
className="w-full max-w-xs bg-gradient-to-r from-emerald-700 to-teal-600 text-white py-4 rounded-2xl font-bold active:scale-[0.97] transition-all">
|
||||||
Sign In
|
Sign In
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,53 +125,76 @@ export default function DhikrPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh flex items-center justify-center bg-[#0a0a0f] pb-20">
|
<div className="min-h-dvh flex items-center justify-center bg-[#f5f4f0] pb-20">
|
||||||
<div className="w-8 h-8 rounded-full border-2 border-[#D4AF37]/30 border-t-[#D4AF37] animate-spin" />
|
<div className="w-8 h-8 rounded-full border-2 border-emerald-700/30 border-t-emerald-700 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] flex flex-col pb-24 select-none">
|
<div className="min-h-dvh bg-[#f5f4f0] flex flex-col pb-24 select-none">
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header gradient */}
|
||||||
<div className="px-5 pt-6 pb-2 flex items-center justify-between">
|
<div className="bg-gradient-to-br from-emerald-700 to-teal-600 rounded-b-3xl px-5 pt-10 pb-8 flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold text-white">Daily Dhikr</h1>
|
<h1 className="text-xl font-bold text-white">Daily Dhikr</h1>
|
||||||
<p className="text-[11px] text-gray-700 mt-0.5">100 remembrances · after prayer</p>
|
<p className="text-[11px] text-white/60 mt-0.5">100 remembrances · after prayer</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 bg-orange-500/10 border border-orange-500/20 rounded-xl px-3 py-2">
|
{/* Streak badge inside header */}
|
||||||
<Flame size={14} className="text-orange-400" />
|
<div className="flex items-center gap-1.5 bg-white/15 border border-white/20 rounded-xl px-3 py-2">
|
||||||
<span className="text-sm font-bold text-orange-400">{streak}</span>
|
<Flame size={14} className="text-orange-300" />
|
||||||
|
<span className="text-sm font-bold text-white">{streak}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Phase progress dots */}
|
{/* Phase tabs — below header */}
|
||||||
<div className="flex gap-2 px-5 mt-4 mb-8">
|
<div className="flex gap-2 px-5 mt-4 mb-2">
|
||||||
{DHIKR_SEQUENCE.map((d, i) => (
|
{DHIKR_SEQUENCE.map((d, i) => {
|
||||||
<div
|
const isActive = i === phase
|
||||||
key={i}
|
const isDone = i < phase
|
||||||
className="flex-1 h-1 rounded-full transition-all duration-500"
|
return (
|
||||||
style={{
|
<div
|
||||||
background: i < phase ? d.ring : i === phase ? `${d.ring}99` : '#1a1a26',
|
key={i}
|
||||||
}}
|
className={`flex-1 py-1.5 rounded-xl text-center transition-all duration-300 ${
|
||||||
/>
|
isActive
|
||||||
))}
|
? 'bg-white shadow-sm border border-[#D4AF37]/40'
|
||||||
|
: isDone
|
||||||
|
? 'bg-white/60 border border-gray-100'
|
||||||
|
: 'bg-white/40 border border-gray-100'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="text-[10px] font-semibold truncate px-1"
|
||||||
|
style={{ color: isActive ? '#D4AF37' : isDone ? d.ring : '#9ca3af' }}
|
||||||
|
>
|
||||||
|
{d.transliteration}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mx-3 mt-1 h-0.5 rounded-full transition-all duration-500"
|
||||||
|
style={{
|
||||||
|
background: isDone ? d.ring : isActive ? `${d.ring}99` : '#e5e7eb',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Completed today (not just-finished) */}
|
{/* Completed today (not just-finished) */}
|
||||||
{todayCompleted && !justFinished ? (
|
{todayCompleted && !justFinished ? (
|
||||||
<div className="flex-1 flex flex-col items-center justify-center px-6 text-center">
|
<div className="flex-1 flex flex-col items-center justify-center px-6 text-center">
|
||||||
<div className="w-28 h-28 rounded-full border-2 border-[#D4AF37]/30 bg-[#D4AF37]/8 flex items-center justify-center mb-6">
|
<div className="bg-white rounded-3xl shadow-md border border-gray-100 p-10 w-full max-w-xs mx-auto flex flex-col items-center">
|
||||||
<CheckCircle2 size={48} className="text-[#D4AF37]" />
|
<div className="w-24 h-24 rounded-full border-2 border-[#D4AF37]/30 bg-amber-50 flex items-center justify-center mb-5">
|
||||||
|
<CheckCircle2 size={44} className="text-[#D4AF37]" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 mb-1">MashaAllah!</h2>
|
||||||
|
<p className="text-gray-500 text-sm mb-5">You've completed your daily dhikr</p>
|
||||||
|
<div className="flex items-center gap-2 bg-orange-50 border border-orange-100 rounded-2xl px-5 py-3">
|
||||||
|
<Flame size={18} className="text-orange-400" />
|
||||||
|
<span className="text-lg font-bold text-orange-500">{streak} day streak</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-white mb-2">MashaAllah!</h2>
|
<button onClick={handleReset} className="mt-8 flex items-center gap-2 text-gray-400 text-sm active:scale-[0.97] transition-transform">
|
||||||
<p className="text-gray-500 text-sm mb-5">You've completed your daily dhikr</p>
|
|
||||||
<div className="flex items-center gap-2 bg-orange-500/10 border border-orange-500/20 rounded-2xl px-5 py-3">
|
|
||||||
<Flame size={18} className="text-orange-400" />
|
|
||||||
<span className="text-lg font-bold text-orange-400">{streak} day streak</span>
|
|
||||||
</div>
|
|
||||||
<button onClick={handleReset} className="mt-10 flex items-center gap-2 text-gray-600 text-sm">
|
|
||||||
<RotateCcw size={13} /> Repeat again
|
<RotateCcw size={13} /> Repeat again
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -180,91 +203,93 @@ export default function DhikrPage() {
|
|||||||
/* Completion screen */
|
/* Completion screen */
|
||||||
<div className="flex-1 flex flex-col items-center justify-center px-6 text-center">
|
<div className="flex-1 flex flex-col items-center justify-center px-6 text-center">
|
||||||
<div className="text-6xl mb-5">✨</div>
|
<div className="text-6xl mb-5">✨</div>
|
||||||
<h2 className="text-3xl font-bold text-white mb-1">SubhanAllah!</h2>
|
<h2 className="text-3xl font-bold text-gray-900 mb-1">SubhanAllah!</h2>
|
||||||
<p className="text-gray-400 text-sm mb-6">100 remembrances complete</p>
|
<p className="text-gray-500 text-sm mb-6">100 remembrances complete</p>
|
||||||
<div className="flex items-center gap-2 bg-orange-500/10 border border-orange-500/20 rounded-2xl px-6 py-4 mb-8">
|
<div className="flex items-center gap-2 bg-orange-50 border border-orange-100 rounded-2xl px-6 py-4 mb-6">
|
||||||
<Flame size={22} className="text-orange-400" />
|
<Flame size={22} className="text-orange-400" />
|
||||||
<span className="text-2xl font-bold text-orange-400">{streak} day streak</span>
|
<span className="text-2xl font-bold text-orange-500">{streak} day streak</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-900/60 border border-gray-800 rounded-2xl px-5 py-4 max-w-xs">
|
<div className="bg-white border border-gray-100 rounded-2xl shadow-sm px-5 py-4 max-w-xs">
|
||||||
<p className="text-xs text-gray-500 leading-relaxed">
|
<p className="text-xs text-gray-500 leading-relaxed">
|
||||||
“Whoever says SubhanAllah 33 times, Alhamdulillah 33 times, and Allahu Akbar 34 times after each prayer…”
|
“Whoever says SubhanAllah 33 times, Alhamdulillah 33 times, and Allahu Akbar 34 times after each prayer…”
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[11px] text-gray-700 mt-2">— Sahih Muslim</p>
|
<p className="text-[11px] text-gray-400 mt-2">— Sahih Muslim</p>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={handleReset} className="mt-8 flex items-center gap-2 text-gray-600 text-sm">
|
<button onClick={handleReset} className="mt-8 flex items-center gap-2 text-gray-400 text-sm active:scale-[0.97] transition-transform">
|
||||||
<RotateCcw size={13} /> Repeat again
|
<RotateCcw size={13} /> Repeat again
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
/* Main counter */
|
/* Main counter */
|
||||||
<div className="flex-1 flex flex-col items-center justify-center px-6">
|
<div className="flex-1 flex flex-col items-center justify-center px-5">
|
||||||
{/* Ring counter */}
|
{/* Ring counter — white card */}
|
||||||
<button
|
<div className="bg-white rounded-3xl shadow-md border border-gray-100 mx-5 py-8 px-6 flex flex-col items-center w-full">
|
||||||
onClick={handleTap}
|
<button
|
||||||
className="relative flex items-center justify-center active:scale-[0.96] transition-transform"
|
onClick={handleTap}
|
||||||
style={{ WebkitTapHighlightColor: 'transparent' }}
|
className="relative flex items-center justify-center active:scale-[0.96] transition-transform"
|
||||||
aria-label={`Tap to count ${current.transliteration}`}
|
style={{ WebkitTapHighlightColor: 'transparent' }}
|
||||||
>
|
aria-label={`Tap to count ${current.transliteration}`}
|
||||||
{/* Outer glow ring */}
|
|
||||||
<div
|
|
||||||
className="absolute rounded-full transition-opacity duration-300"
|
|
||||||
style={{
|
|
||||||
width: 216, height: 216,
|
|
||||||
background: `radial-gradient(circle, ${current.glow} 0%, transparent 70%)`,
|
|
||||||
opacity: ripple ? 1 : 0.4,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* SVG progress ring */}
|
|
||||||
<svg width="200" height="200" className="-rotate-90" style={{ filter: `drop-shadow(0 0 8px ${current.glow})` }}>
|
|
||||||
{/* Track */}
|
|
||||||
<circle cx="100" cy="100" r={R} fill="none" stroke="#1a1a26" strokeWidth="8" />
|
|
||||||
{/* Progress */}
|
|
||||||
<circle
|
|
||||||
cx="100" cy="100" r={R} fill="none"
|
|
||||||
stroke={current.ring}
|
|
||||||
strokeWidth="8"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeDasharray={C}
|
|
||||||
strokeDashoffset={strokeDashoffset}
|
|
||||||
className="transition-all duration-200"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
{/* Center content */}
|
|
||||||
<div
|
|
||||||
className="absolute rounded-full flex flex-col items-center justify-center border border-white/5"
|
|
||||||
style={{
|
|
||||||
inset: 28,
|
|
||||||
background: `radial-gradient(circle at 40% 30%, ${current.bg}, #0d0d18)`,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<span className="text-5xl font-bold text-white tabular-nums leading-none">{count}</span>
|
{/* Outer glow ring */}
|
||||||
<span className="text-[11px] text-gray-600 mt-1">of {current.target}</span>
|
<div
|
||||||
</div>
|
className="absolute rounded-full transition-opacity duration-300"
|
||||||
</button>
|
style={{
|
||||||
|
width: 216, height: 216,
|
||||||
|
background: `radial-gradient(circle, ${current.glow} 0%, transparent 70%)`,
|
||||||
|
opacity: ripple ? 1 : 0.5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Dhikr text */}
|
{/* SVG progress ring */}
|
||||||
<div className="text-center mt-10 space-y-2">
|
<svg width="200" height="200" className="-rotate-90" style={{ filter: `drop-shadow(0 0 8px ${current.glow})` }}>
|
||||||
<p className="text-3xl text-white leading-relaxed font-light">{current.arabic}</p>
|
{/* Track */}
|
||||||
<p className="font-bold text-base" style={{ color: current.ring }}>{current.transliteration}</p>
|
<circle cx="100" cy="100" r={R} fill="none" stroke="#e5e7eb" strokeWidth="8" />
|
||||||
<p className="text-gray-600 text-sm">{current.meaning}</p>
|
{/* Progress */}
|
||||||
|
<circle
|
||||||
|
cx="100" cy="100" r={R} fill="none"
|
||||||
|
stroke={current.ring}
|
||||||
|
strokeWidth="8"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray={C}
|
||||||
|
strokeDashoffset={strokeDashoffset}
|
||||||
|
className="transition-all duration-200"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Center content */}
|
||||||
|
<div
|
||||||
|
className="absolute rounded-full flex flex-col items-center justify-center"
|
||||||
|
style={{
|
||||||
|
inset: 28,
|
||||||
|
background: `radial-gradient(circle at 40% 30%, ${current.bg}, #ffffff)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="text-5xl font-bold text-gray-900 tabular-nums leading-none">{count}</span>
|
||||||
|
<span className="text-[11px] text-gray-400 mt-1">of {current.target}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Dhikr text below ring, inside card */}
|
||||||
|
<div className="text-center mt-6 space-y-1.5">
|
||||||
|
<p className="text-3xl text-gray-900 leading-relaxed font-light">{current.arabic}</p>
|
||||||
|
<p className="font-bold text-base" style={{ color: current.ring }}>{current.transliteration}</p>
|
||||||
|
<p className="text-gray-500 text-sm">{current.meaning}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-gray-800 text-xs mt-10">Tap to count</p>
|
<p className="text-gray-400 text-xs mt-6">Tap to count</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Bottom progress */}
|
{/* Bottom progress bar */}
|
||||||
{!justFinished && (
|
{!justFinished && (
|
||||||
<div className="px-5 pb-2">
|
<div className="px-5 pb-2 mt-4">
|
||||||
<div className="flex items-center justify-between text-[11px] text-gray-700 mb-2">
|
<div className="flex items-center justify-between text-[11px] text-gray-400 mb-2">
|
||||||
<span>Total today</span>
|
<span>Total today</span>
|
||||||
<span className="tabular-nums">{Math.min(totalCount, TOTAL)} / {TOTAL}</span>
|
<span className="tabular-nums">{Math.min(totalCount, TOTAL)} / {TOTAL}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-1 bg-gray-900 rounded-full overflow-hidden">
|
<div className="h-1.5 bg-gray-200 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-full rounded-full transition-all duration-300"
|
className="h-full rounded-full transition-all duration-300"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
+36
-36
@@ -39,19 +39,19 @@ export default function ForumPage() {
|
|||||||
const showToast = (msg: string) => { setToast(msg); setTimeout(() => setToast(''), 3000) }
|
const showToast = (msg: string) => { setToast(msg); setTimeout(() => setToast(''), 3000) }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] pb-24">
|
<div className="min-h-dvh bg-[#f5f4f0] pb-24">
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="px-5 pt-6 pb-4 flex items-center justify-between">
|
<div className="px-5 pt-8 pb-4 bg-white flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{view === 'threads' && (
|
{view === 'threads' && (
|
||||||
<button onClick={() => { setView('categories'); setSelectedCat(null) }} className="w-9 h-9 flex items-center justify-center rounded-xl bg-gray-800/60 active:opacity-60">
|
<button onClick={() => { setView('categories'); setSelectedCat(null) }} className="w-9 h-9 flex items-center justify-center rounded-xl bg-gray-100 active:opacity-60">
|
||||||
<ArrowLeft size={18} className="text-gray-400" />
|
<ArrowLeft size={18} className="text-gray-500" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold text-white">{view === 'threads' && selectedCat ? selectedCat.name : 'Forum'}</h1>
|
<h1 className="text-xl font-bold text-gray-900">{view === 'threads' && selectedCat ? selectedCat.name : 'Forum'}</h1>
|
||||||
<p className="text-[11px] text-gray-600 mt-0.5">
|
<p className="text-[11px] text-gray-400 mt-0.5">
|
||||||
{view === 'categories' ? 'Community discussions' : `${threads.length} threads`}
|
{view === 'categories' ? 'Community discussions' : `${threads.length} threads`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,7 +59,7 @@ export default function ForumPage() {
|
|||||||
{token && (
|
{token && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCreate(true)}
|
onClick={() => setShowCreate(true)}
|
||||||
className="flex items-center gap-1.5 bg-[#D4AF37] text-[#0a0a0f] px-4 py-2.5 rounded-xl font-bold text-sm active:scale-[0.96] transition-transform"
|
className="flex items-center gap-1.5 bg-emerald-600 text-white px-4 py-2.5 rounded-xl font-bold text-sm active:scale-[0.97] transition-transform"
|
||||||
>
|
>
|
||||||
<Plus size={16} /> Post
|
<Plus size={16} /> Post
|
||||||
</button>
|
</button>
|
||||||
@@ -68,36 +68,36 @@ export default function ForumPage() {
|
|||||||
|
|
||||||
{/* Loading */}
|
{/* Loading */}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="px-5 space-y-3">
|
<div className="px-5 pt-4 space-y-3">
|
||||||
{[...Array(4)].map((_, i) => (
|
{[...Array(4)].map((_, i) => (
|
||||||
<div key={i} className="h-20 rounded-2xl bg-gray-900/60 animate-pulse" />
|
<div key={i} className="h-20 rounded-2xl bg-gray-200 animate-pulse" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Categories view */}
|
{/* Categories view */}
|
||||||
{!loading && view === 'categories' && (
|
{!loading && view === 'categories' && (
|
||||||
<div className="px-5 space-y-3">
|
<div className="px-5 pt-4 space-y-3">
|
||||||
{categories.length === 0 ? (
|
{categories.length === 0 ? (
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16">
|
||||||
<div className="text-4xl mb-3">🕌</div>
|
<div className="text-4xl mb-3">🕌</div>
|
||||||
<p className="text-gray-500 text-sm">No categories yet</p>
|
<p className="text-gray-400 text-sm">No categories yet</p>
|
||||||
</div>
|
</div>
|
||||||
) : categories.map(c => (
|
) : categories.map(c => (
|
||||||
<button
|
<button
|
||||||
key={c.id}
|
key={c.id}
|
||||||
onClick={() => { setSelectedCat(c); setView('threads'); loadThreads(c.id) }}
|
onClick={() => { setSelectedCat(c); setView('threads'); loadThreads(c.id) }}
|
||||||
className="w-full text-left bg-[#111118] border border-gray-800/60 rounded-2xl p-4 active:scale-[0.99] transition-transform"
|
className="w-full text-left bg-white rounded-2xl shadow-sm border border-gray-100 p-4 active:scale-[0.97] transition-transform"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-2xl w-10 h-10 flex items-center justify-center bg-gray-800/60 rounded-xl shrink-0">
|
<span className="text-2xl w-10 h-10 flex items-center justify-center bg-emerald-50 rounded-xl shrink-0">
|
||||||
{c.icon || '💬'}
|
{c.icon || '💬'}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-semibold text-white text-sm">{c.name}</h3>
|
<h3 className="font-semibold text-gray-900 text-sm">{c.name}</h3>
|
||||||
<p className="text-xs text-gray-500 mt-0.5 line-clamp-1">{c.description}</p>
|
<p className="text-xs text-gray-400 mt-0.5 line-clamp-1">{c.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight size={16} className="text-gray-700 shrink-0" />
|
<ChevronRight size={16} className="text-gray-300 shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -106,25 +106,25 @@ export default function ForumPage() {
|
|||||||
|
|
||||||
{/* Threads view */}
|
{/* Threads view */}
|
||||||
{!loading && view === 'threads' && (
|
{!loading && view === 'threads' && (
|
||||||
<div className="px-5 space-y-3">
|
<div className="px-5 pt-4 space-y-3">
|
||||||
{threads.length === 0 ? (
|
{threads.length === 0 ? (
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16">
|
||||||
<div className="text-4xl mb-3">💬</div>
|
<div className="text-4xl mb-3">💬</div>
|
||||||
<p className="text-white font-semibold mb-1">No threads yet</p>
|
<p className="text-gray-900 font-semibold mb-1">No threads yet</p>
|
||||||
<p className="text-gray-500 text-sm">Start the first discussion</p>
|
<p className="text-gray-400 text-sm">Start the first discussion</p>
|
||||||
</div>
|
</div>
|
||||||
) : threads.map(t => (
|
) : threads.map(t => (
|
||||||
<div key={t.id} className="bg-[#111118] border border-gray-800/60 rounded-2xl p-4">
|
<div key={t.id} className="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||||
<h3 className="font-semibold text-white text-sm leading-snug mb-1">{t.title}</h3>
|
<h3 className="font-semibold text-gray-900 text-sm leading-snug mb-1">{t.title}</h3>
|
||||||
<p className="text-xs text-gray-500 line-clamp-2 mb-3">{t.content}</p>
|
<p className="text-xs text-gray-400 line-clamp-2 mb-3">{t.content}</p>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<div className="w-5 h-5 rounded-full bg-[#D4AF37]/15 flex items-center justify-center">
|
<div className="w-5 h-5 rounded-full bg-emerald-100 flex items-center justify-center">
|
||||||
<span className="text-[9px] text-[#D4AF37] font-bold">{t.author.name[0]}</span>
|
<span className="text-[9px] text-emerald-700 font-bold">{t.author.name[0]}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[11px] text-gray-600">{t.author.name}</span>
|
<span className="text-[11px] text-gray-400">{t.author.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 text-[11px] text-gray-700">
|
<div className="flex items-center gap-1 text-[11px] text-gray-400">
|
||||||
<MessageCircle size={12} />
|
<MessageCircle size={12} />
|
||||||
{t._count.posts}
|
{t._count.posts}
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +136,7 @@ export default function ForumPage() {
|
|||||||
|
|
||||||
{/* Toast */}
|
{/* Toast */}
|
||||||
{toast && (
|
{toast && (
|
||||||
<div className="fixed bottom-24 left-5 right-5 bg-[#1a1a26] border border-gray-700 rounded-2xl px-4 py-3 text-sm text-white text-center z-50 shadow-xl">
|
<div className="fixed bottom-24 left-5 right-5 bg-gray-900 text-white rounded-2xl px-4 py-3 text-sm text-center z-50 shadow-xl">
|
||||||
{toast}
|
{toast}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -182,38 +182,38 @@ function CreateThreadSheet({ token, categories, onClose, onCreated, showToast }:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex flex-col justify-end" onClick={onClose}>
|
<div className="fixed inset-0 z-50 flex flex-col justify-end" onClick={onClose}>
|
||||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" />
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="relative bg-[#0f0f18] border border-gray-800/60 rounded-t-3xl p-6 pb-10 space-y-4"
|
className="relative bg-white rounded-t-3xl p-6 pb-10 space-y-4"
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<div className="w-10 h-1 bg-gray-700 rounded-full mx-auto absolute left-1/2 -translate-x-1/2 top-3" />
|
<div className="w-10 h-1 bg-gray-200 rounded-full mx-auto absolute left-1/2 -translate-x-1/2 top-3" />
|
||||||
<h2 className="text-lg font-bold text-white mt-2">New Thread</h2>
|
<h2 className="text-lg font-bold text-gray-900 mt-2">New Thread</h2>
|
||||||
<button type="button" onClick={onClose} className="w-8 h-8 flex items-center justify-center rounded-full bg-gray-800 mt-2">
|
<button type="button" onClick={onClose} className="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 mt-2">
|
||||||
<X size={15} className="text-gray-400" />
|
<X size={15} className="text-gray-400" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={categoryId} onChange={e => setCategoryId(e.target.value)}
|
value={categoryId} onChange={e => setCategoryId(e.target.value)}
|
||||||
className="w-full bg-[#0a0a0f] border border-gray-800 rounded-2xl px-4 py-4 text-sm text-white focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3.5 text-sm text-gray-900 focus:border-emerald-400 focus:outline-none"
|
||||||
>
|
>
|
||||||
{categories.map(c => <option key={c.id} value={c.id}>{c.icon} {c.name}</option>)}
|
{categories.map(c => <option key={c.id} value={c.id}>{c.icon} {c.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text" placeholder="Thread title" value={title} onChange={e => setTitle(e.target.value)} required
|
type="text" placeholder="Thread title" value={title} onChange={e => setTitle(e.target.value)} required
|
||||||
className="w-full bg-[#0a0a0f] border border-gray-800 rounded-2xl px-4 py-4 text-sm text-white placeholder-gray-600 focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
placeholder="Share your thoughts…" value={content} onChange={e => setContent(e.target.value)} required rows={4}
|
placeholder="Share your thoughts…" value={content} onChange={e => setContent(e.target.value)} required rows={4}
|
||||||
className="w-full bg-[#0a0a0f] border border-gray-800 rounded-2xl px-4 py-3 text-sm text-white placeholder-gray-600 focus:border-[#D4AF37]/50 focus:outline-none resize-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none resize-none"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit" disabled={loading}
|
type="submit" disabled={loading}
|
||||||
className="w-full bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold disabled:opacity-50 active:scale-[0.98] transition-transform"
|
className="w-full bg-emerald-600 text-white py-4 rounded-2xl font-bold disabled:opacity-50 active:scale-[0.97] transition-transform"
|
||||||
>
|
>
|
||||||
{loading ? 'Posting…' : 'Post Thread'}
|
{loading ? 'Posting…' : 'Post Thread'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+9
-6
@@ -3,16 +3,16 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #0a0a0f;
|
--background: #f5f4f0;
|
||||||
--foreground: #ededed;
|
--foreground: #111827;
|
||||||
--gold: #D4AF37;
|
--gold: #D4AF37;
|
||||||
--gold-dark: #C9A84C;
|
--emerald: #059669;
|
||||||
--nav-height: 64px;
|
--nav-height: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color-scheme: dark;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -37,13 +37,16 @@ a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent iOS zoom on input focus — font-size must be >= 16px */
|
/* Prevent iOS zoom on input focus */
|
||||||
input, select, textarea {
|
input, select, textarea {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Smooth scrolling in chat areas */
|
|
||||||
.chat-scroll {
|
.chat-scroll {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar on category strips */
|
||||||
|
.scrollbar-none::-webkit-scrollbar { display: none; }
|
||||||
|
.scrollbar-none { -ms-overflow-style: none; scrollbar-width: none; }
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
|
import { useAuth } from '@/lib/AuthContext'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { Bell, ChevronRight, Flame, BookOpen } from 'lucide-react'
|
||||||
|
|
||||||
|
type Timings = { Fajr: string; Sunrise: string; Dhuhr: string; Asr: string; Maghrib: string; Isha: string }
|
||||||
|
const PRAYERS = ['Fajr', 'Dhuhr', 'Asr', 'Maghrib', 'Isha'] as const
|
||||||
|
type PrayerName = typeof PRAYERS[number]
|
||||||
|
|
||||||
|
function toMins(t: string) { const [h, m] = t.replace(/\s*(AM|PM)$/i, '').split(':').map(Number); return h * 60 + m }
|
||||||
|
function fmt12(t: string) { const [h, m] = t.replace(/\s*(AM|PM)$/i, '').split(':').map(Number); return `${h % 12 || 12}:${String(m).padStart(2,'0')} ${h < 12 ? 'AM' : 'PM'}` }
|
||||||
|
function fmtCountdown(mins: number) {
|
||||||
|
const h = Math.floor(mins / 60), m = mins % 60
|
||||||
|
if (h === 0) return `${m}m`
|
||||||
|
return m === 0 ? `${h}h` : `${h}h ${m}m`
|
||||||
|
}
|
||||||
|
|
||||||
|
const FEATURES = [
|
||||||
|
{ href: '/nur', emoji: '🤖', label: 'Nur AI', bg: 'from-violet-500 to-purple-600' },
|
||||||
|
{ href: '/prayer', emoji: '🕌', label: 'Prayer', bg: 'from-emerald-500 to-teal-600' },
|
||||||
|
{ href: '/dhikr', emoji: '📿', label: 'Dhikr', bg: 'from-teal-500 to-cyan-600' },
|
||||||
|
{ href: '/souq', emoji: '🛍️', label: 'Souq', bg: 'from-amber-500 to-orange-500' },
|
||||||
|
{ href: '/halal-monitor', emoji: '🗺️', label: 'Halal Map', bg: 'from-green-500 to-emerald-600' },
|
||||||
|
{ href: '/forum', emoji: '💬', label: 'Forum', bg: 'from-blue-500 to-indigo-600' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
const { user, token } = useAuth()
|
||||||
|
const router = useRouter()
|
||||||
|
const [timings, setTimings] = useState<Timings | null>(null)
|
||||||
|
const [next, setNext] = useState<{ name: PrayerName; minsLeft: number } | null>(null)
|
||||||
|
const [city, setCity] = useState('')
|
||||||
|
const [now, setNow] = useState(new Date())
|
||||||
|
const [verse, setVerse] = useState<{ surah: string; translation: string; arabic: string } | null>(null)
|
||||||
|
const [streak, setStreak] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => { const id = setInterval(() => setNow(new Date()), 60000); return () => clearInterval(id) }, [])
|
||||||
|
useEffect(() => { fetch('/api/daily/verse').then(r => r.json()).then(setVerse).catch(() => {}) }, [])
|
||||||
|
useEffect(() => {
|
||||||
|
if (!token) return
|
||||||
|
fetch('/api/dhikr/status', { headers: { Authorization: `Bearer ${token}` } })
|
||||||
|
.then(r => r.json()).then(d => setStreak(d.streak ?? 0)).catch(() => {})
|
||||||
|
}, [token])
|
||||||
|
|
||||||
|
const fetchPrayer = useCallback((lat: number, lng: number) => {
|
||||||
|
fetch(`/api/prayer/times?lat=${lat}&lng=${lng}`)
|
||||||
|
.then(r => r.json()).then(d => {
|
||||||
|
if (d.timings) {
|
||||||
|
setTimings(d.timings)
|
||||||
|
const nowMins = now.getHours() * 60 + now.getMinutes()
|
||||||
|
for (const p of PRAYERS) {
|
||||||
|
const pm = toMins(d.timings[p])
|
||||||
|
if (pm > nowMins) { setNext({ name: p, minsLeft: pm - nowMins }); return }
|
||||||
|
}
|
||||||
|
setNext({ name: 'Fajr', minsLeft: (24 * 60 - nowMins) + toMins(d.timings.Fajr) })
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}, [now])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!navigator.geolocation) return
|
||||||
|
navigator.geolocation.getCurrentPosition(pos => {
|
||||||
|
const { latitude: lat, longitude: lng } = pos.coords
|
||||||
|
fetchPrayer(lat, lng)
|
||||||
|
fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json`, { headers: { 'Accept-Language': 'en' } })
|
||||||
|
.then(r => r.json()).then(d => setCity(d.address?.city || d.address?.town || '')).catch(() => {})
|
||||||
|
}, () => {}, { timeout: 8000 })
|
||||||
|
}, [fetchPrayer])
|
||||||
|
|
||||||
|
const greeting = (() => {
|
||||||
|
const h = now.getHours()
|
||||||
|
if (h < 12) return 'Good Morning'
|
||||||
|
if (h < 17) return 'Good Afternoon'
|
||||||
|
return 'Good Evening'
|
||||||
|
})()
|
||||||
|
|
||||||
|
const name = user?.preferredName || user?.name?.split(' ')[0] || 'Guest'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-dvh bg-[#f5f4f0] pb-24 select-none">
|
||||||
|
|
||||||
|
{/* Header gradient */}
|
||||||
|
<div className="bg-gradient-to-br from-emerald-700 via-teal-600 to-emerald-800 px-5 pt-12 pb-6 rounded-b-[32px]">
|
||||||
|
<div className="flex items-start justify-between mb-6">
|
||||||
|
<div>
|
||||||
|
<p className="text-emerald-200 text-xs font-medium mb-0.5">{greeting}</p>
|
||||||
|
<h1 className="text-2xl font-bold text-white">Assalamualaikum</h1>
|
||||||
|
<p className="text-white/80 text-sm mt-0.5">{name}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{streak > 0 && (
|
||||||
|
<div className="flex items-center gap-1 bg-white/15 rounded-xl px-2.5 py-1.5">
|
||||||
|
<Flame size={13} className="text-orange-300" />
|
||||||
|
<span className="text-xs font-bold text-white">{streak}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button className="w-9 h-9 bg-white/15 rounded-xl flex items-center justify-center">
|
||||||
|
<Bell size={18} className="text-white" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Prayer time card inside header */}
|
||||||
|
<div
|
||||||
|
className="bg-white/15 backdrop-blur-sm rounded-2xl p-4 border border-white/20"
|
||||||
|
onClick={() => router.push('/prayer')}
|
||||||
|
>
|
||||||
|
{next && timings ? (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-emerald-200 text-[11px] font-medium uppercase tracking-wider mb-1">Next Prayer</p>
|
||||||
|
<p className="text-white text-xl font-bold">{next.name}</p>
|
||||||
|
<p className="text-white/70 text-sm">{fmt12(timings[next.name])}</p>
|
||||||
|
{city && <p className="text-white/50 text-[11px] mt-1">{city}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<p className="text-3xl font-bold text-white tabular-nums">{fmtCountdown(next.minsLeft)}</p>
|
||||||
|
<p className="text-white/60 text-xs mt-1">remaining</p>
|
||||||
|
<div className="flex items-center gap-1 justify-end mt-2">
|
||||||
|
<span className="text-white/50 text-[11px]">View all</span>
|
||||||
|
<ChevronRight size={11} className="text-white/50" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-emerald-200 text-[11px] font-medium uppercase tracking-wider mb-2">Prayer Times</p>
|
||||||
|
<p className="text-white/70 text-sm">Tap to enable location</p>
|
||||||
|
</div>
|
||||||
|
<ChevronRight size={18} className="text-white/50" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Features grid */}
|
||||||
|
<div className="px-5 mt-6 mb-6">
|
||||||
|
<h2 className="text-sm font-bold text-gray-900 mb-3">Quick Access</h2>
|
||||||
|
<div className="grid grid-cols-3 gap-3">
|
||||||
|
{FEATURES.map(f => (
|
||||||
|
<Link
|
||||||
|
key={f.href}
|
||||||
|
href={f.href}
|
||||||
|
className="flex flex-col items-center gap-2 active:scale-[0.94] transition-transform"
|
||||||
|
>
|
||||||
|
<div className={`w-16 h-16 rounded-2xl bg-gradient-to-br ${f.bg} flex items-center justify-center shadow-sm`}>
|
||||||
|
<span className="text-2xl">{f.emoji}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-semibold text-gray-700">{f.label}</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Daily verse */}
|
||||||
|
{verse && (
|
||||||
|
<div className="mx-5 mb-5 bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-amber-500 to-yellow-500 px-4 py-2.5 flex items-center gap-2">
|
||||||
|
<BookOpen size={14} className="text-white" />
|
||||||
|
<span className="text-white text-xs font-bold uppercase tracking-wider">Daily Verse</span>
|
||||||
|
<span className="text-white/70 text-xs ml-auto">{verse.surah}</span>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 py-4">
|
||||||
|
<p className="text-right text-xl text-gray-800 leading-loose mb-3 font-light">{verse.arabic}</p>
|
||||||
|
<p className="text-gray-600 text-sm leading-relaxed italic">“{verse.translation}”</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Nur AI promo */}
|
||||||
|
<div className="mx-5 mb-5">
|
||||||
|
<Link href="/nur" className="block bg-gradient-to-r from-violet-600 to-purple-700 rounded-2xl p-4 active:scale-[0.98] transition-transform">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-white font-bold text-base">Ask Nur AI</p>
|
||||||
|
<p className="text-white/70 text-xs mt-0.5">Your personal Islamic guide</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center">
|
||||||
|
<span className="text-xl">🤖</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sign in prompt for guests */}
|
||||||
|
{!user && (
|
||||||
|
<div className="mx-5 mb-5 bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||||
|
<p className="text-gray-900 font-semibold text-sm mb-1">Sign in to track your progress</p>
|
||||||
|
<p className="text-gray-500 text-xs mb-3">Streaks, history, and personalization</p>
|
||||||
|
<Link href="/login" className="block w-full text-center bg-emerald-600 text-white py-3 rounded-xl font-semibold text-sm active:scale-[0.98] transition-transform">
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+12
-12
@@ -30,13 +30,13 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh flex flex-col bg-[#0a0a0f] px-6">
|
<div className="min-h-dvh flex flex-col bg-white px-6">
|
||||||
{/* Brand */}
|
{/* Brand */}
|
||||||
<div className="flex flex-col items-center pt-16 pb-10">
|
<div className="flex flex-col items-center pt-16 pb-10">
|
||||||
<div className="w-20 h-20 rounded-3xl bg-[#D4AF37]/10 border border-[#D4AF37]/25 flex items-center justify-center mb-5">
|
<div className="w-20 h-20 rounded-3xl bg-gradient-to-br from-emerald-600 to-teal-500 flex items-center justify-center mb-5 shadow-lg shadow-emerald-200">
|
||||||
<span className="text-3xl font-bold text-[#D4AF37]">ف</span>
|
<span className="text-3xl font-bold text-white">ف</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold text-white tracking-tight">Welcome back</h1>
|
<h1 className="text-2xl font-bold text-gray-900 tracking-tight">Welcome back</h1>
|
||||||
<p className="text-gray-500 text-sm mt-1">Sign in to your Falah account</p>
|
<p className="text-gray-500 text-sm mt-1">Sign in to your Falah account</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ export default function LoginPage() {
|
|||||||
required
|
required
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
inputMode="email"
|
inputMode="email"
|
||||||
className="w-full bg-[#111118] border border-gray-800 rounded-2xl px-4 py-4 text-white placeholder-gray-700 focus:border-[#D4AF37]/60 focus:outline-none transition"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none transition"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -66,12 +66,12 @@ export default function LoginPage() {
|
|||||||
onChange={e => setPassword(e.target.value)}
|
onChange={e => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
className="w-full bg-[#111118] border border-gray-800 rounded-2xl px-4 py-4 pr-14 text-white placeholder-gray-700 focus:border-[#D4AF37]/60 focus:outline-none transition"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 pr-14 text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none transition"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPass(v => !v)}
|
onClick={() => setShowPass(v => !v)}
|
||||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-600 p-1"
|
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 p-1"
|
||||||
>
|
>
|
||||||
{showPass ? <EyeOff size={20} /> : <Eye size={20} />}
|
{showPass ? <EyeOff size={20} /> : <Eye size={20} />}
|
||||||
</button>
|
</button>
|
||||||
@@ -79,15 +79,15 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-red-500/10 border border-red-500/20 rounded-2xl px-4 py-3">
|
<div className="bg-red-50 border border-red-200 rounded-xl px-4 py-3">
|
||||||
<p className="text-red-400 text-sm">{error}</p>
|
<p className="text-red-500 text-sm">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="w-full bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold text-base disabled:opacity-50 active:scale-[0.98] transition-all mt-2"
|
className="w-full bg-emerald-600 text-white py-4 rounded-2xl font-bold text-base disabled:opacity-50 active:scale-[0.97] transition-all mt-2 shadow-sm shadow-emerald-200"
|
||||||
>
|
>
|
||||||
{loading ? 'Signing in...' : 'Sign In'}
|
{loading ? 'Signing in...' : 'Sign In'}
|
||||||
</button>
|
</button>
|
||||||
@@ -96,11 +96,11 @@ export default function LoginPage() {
|
|||||||
<div className="py-8 space-y-4 text-center">
|
<div className="py-8 space-y-4 text-center">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
No account?{' '}
|
No account?{' '}
|
||||||
<Link href="/register" className="text-[#D4AF37] font-semibold">
|
<Link href="/register" className="text-emerald-600 font-semibold">
|
||||||
Create one free
|
Create one free
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-800">بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ</p>
|
<p className="text-xs text-gray-300">بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+59
-59
@@ -173,14 +173,14 @@ export default function NurPage() {
|
|||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh flex flex-col items-center justify-center bg-[#0a0a0f] px-6 pb-20">
|
<div className="min-h-dvh flex flex-col items-center justify-center bg-[#f5f4f0] px-6 pb-20">
|
||||||
<div className="w-20 h-20 rounded-3xl bg-[#D4AF37]/10 border border-[#D4AF37]/20 flex items-center justify-center mb-6">
|
<div className="w-20 h-20 rounded-3xl bg-gradient-to-br from-violet-500 to-purple-600 flex items-center justify-center mb-6 shadow-lg">
|
||||||
<Bot size={36} className="text-[#D4AF37]" />
|
<Bot size={36} className="text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-xl font-bold text-white mb-2">Meet Nur</h2>
|
<h2 className="text-xl font-bold text-gray-900 mb-2">Meet Nur</h2>
|
||||||
<p className="text-gray-500 text-sm text-center mb-8">Your personal Islamic guide. Sign in to start your journey.</p>
|
<p className="text-gray-500 text-sm text-center mb-8">Your personal Islamic guide. Sign in to start your journey.</p>
|
||||||
<button onClick={() => router.push('/login')}
|
<button onClick={() => router.push('/login')}
|
||||||
className="w-full max-w-xs bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold text-base active:scale-[0.98] transition-all">
|
className="w-full max-w-xs bg-emerald-600 text-white py-4 rounded-2xl font-bold text-base active:scale-[0.97] transition-all">
|
||||||
Sign In
|
Sign In
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,20 +188,20 @@ export default function NurPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col bg-[#0a0a0f]" style={{ height: 'calc(100dvh - 64px)' }}>
|
<div className="flex flex-col bg-[#f5f4f0]" style={{ height: 'calc(100dvh - 64px)' }}>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="shrink-0 flex items-center gap-3 px-4 py-3 border-b border-gray-800/60 bg-[#0a0a0f]">
|
<div className="shrink-0 flex items-center gap-3 px-4 py-3 border-b border-gray-100 bg-white">
|
||||||
<div className={`w-10 h-10 rounded-2xl flex items-center justify-center bg-[#D4AF37]/10 border border-[#D4AF37]/20`}>
|
<div className="w-10 h-10 rounded-2xl flex items-center justify-center bg-violet-50 border border-violet-100">
|
||||||
<PersonaIcon size={20} className={PERSONA_COLORS[profile.coachPersona]} />
|
<PersonaIcon size={20} className={PERSONA_COLORS[profile.coachPersona]} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h1 className="font-bold text-white text-sm leading-tight truncate">{currentPersona.name}</h1>
|
<h1 className="font-bold text-gray-900 text-sm leading-tight truncate">{currentPersona.name}</h1>
|
||||||
<p className="text-gray-500 text-xs truncate">{currentPersona.title}</p>
|
<p className="text-gray-400 text-xs truncate">{currentPersona.title}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowSettings(true)}
|
onClick={() => setShowSettings(true)}
|
||||||
className="w-9 h-9 flex items-center justify-center rounded-xl bg-gray-900 text-gray-400 active:bg-gray-800 transition"
|
className="w-9 h-9 flex items-center justify-center rounded-xl bg-gray-100 text-gray-500 active:bg-gray-200 transition"
|
||||||
>
|
>
|
||||||
<Settings size={17} />
|
<Settings size={17} />
|
||||||
</button>
|
</button>
|
||||||
@@ -209,20 +209,20 @@ export default function NurPage() {
|
|||||||
|
|
||||||
{/* Daily Verse Banner */}
|
{/* Daily Verse Banner */}
|
||||||
{dailyVerse && (
|
{dailyVerse && (
|
||||||
<div className="shrink-0 border-b border-gray-800/60">
|
<div className="shrink-0 border-b border-amber-100 bg-amber-50">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowVerse(v => !v)}
|
onClick={() => setShowVerse(v => !v)}
|
||||||
className="w-full flex items-center gap-2 px-4 py-2.5 text-left"
|
className="w-full flex items-center gap-2 px-4 py-2.5 text-left"
|
||||||
>
|
>
|
||||||
<BookMarked size={13} className="text-[#D4AF37] shrink-0" />
|
<BookMarked size={13} className="text-amber-600 shrink-0" />
|
||||||
<span className="text-xs font-semibold text-[#D4AF37]">Today's Verse</span>
|
<span className="text-xs font-semibold text-amber-700">Today's Verse</span>
|
||||||
<span className="text-xs text-gray-600 truncate flex-1">{dailyVerse.surah} {dailyVerse.ayah}</span>
|
<span className="text-xs text-amber-500 truncate flex-1">{dailyVerse.surah} {dailyVerse.ayah}</span>
|
||||||
{showVerse ? <ChevronUp size={13} className="text-gray-600 shrink-0" /> : <ChevronDown size={13} className="text-gray-600 shrink-0" />}
|
{showVerse ? <ChevronUp size={13} className="text-amber-400 shrink-0" /> : <ChevronDown size={13} className="text-amber-400 shrink-0" />}
|
||||||
</button>
|
</button>
|
||||||
{showVerse && (
|
{showVerse && (
|
||||||
<div className="px-4 pb-4 space-y-2 bg-[#0d0d14]">
|
<div className="px-4 pb-4 space-y-2">
|
||||||
<p className="text-right text-lg leading-loose text-gray-200">{dailyVerse.arabic}</p>
|
<p className="text-right text-lg leading-loose text-gray-900">{dailyVerse.arabic}</p>
|
||||||
<p className="text-xs text-gray-400 italic">“{dailyVerse.translation}”</p>
|
<p className="text-xs text-gray-600 italic">“{dailyVerse.translation}”</p>
|
||||||
<p className="text-xs text-gray-500 leading-relaxed">{dailyVerse.reflection}</p>
|
<p className="text-xs text-gray-500 leading-relaxed">{dailyVerse.reflection}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -233,7 +233,7 @@ export default function NurPage() {
|
|||||||
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-3 chat-scroll">
|
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-3 chat-scroll">
|
||||||
{messages.length === 0 && !dailyLoading ? (
|
{messages.length === 0 && !dailyLoading ? (
|
||||||
<div className="flex flex-col items-center justify-center h-full pb-8 space-y-3">
|
<div className="flex flex-col items-center justify-center h-full pb-8 space-y-3">
|
||||||
<div className="w-16 h-16 rounded-3xl bg-[#D4AF37]/10 border border-[#D4AF37]/15 flex items-center justify-center">
|
<div className="w-16 h-16 rounded-3xl bg-violet-50 border border-violet-100 flex items-center justify-center">
|
||||||
<PersonaIcon size={28} className={PERSONA_COLORS[profile.coachPersona]} />
|
<PersonaIcon size={28} className={PERSONA_COLORS[profile.coachPersona]} />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-500 text-sm text-center max-w-xs px-4">{currentPersona.greeting}</p>
|
<p className="text-gray-500 text-sm text-center max-w-xs px-4">{currentPersona.greeting}</p>
|
||||||
@@ -242,31 +242,31 @@ export default function NurPage() {
|
|||||||
messages.map((m, i) => (
|
messages.map((m, i) => (
|
||||||
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||||
{m.role === 'assistant' && (
|
{m.role === 'assistant' && (
|
||||||
<div className="w-7 h-7 rounded-xl bg-[#D4AF37]/10 flex items-center justify-center mr-2 shrink-0 mt-0.5">
|
<div className="w-7 h-7 rounded-xl bg-violet-50 flex items-center justify-center mr-2 shrink-0 mt-0.5">
|
||||||
<PersonaIcon size={13} className={PERSONA_COLORS[profile.coachPersona]} />
|
<PersonaIcon size={13} className={PERSONA_COLORS[profile.coachPersona]} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`max-w-[78%] rounded-2xl px-4 py-3 ${
|
<div className={`max-w-[78%] rounded-2xl px-4 py-3 ${
|
||||||
m.role === 'user'
|
m.role === 'user'
|
||||||
? 'bg-[#D4AF37] text-[#0a0a0f] rounded-br-sm'
|
? 'bg-emerald-600 text-white rounded-br-sm'
|
||||||
: 'bg-[#1a1a26] text-gray-100 rounded-bl-sm'
|
: 'bg-white text-gray-900 rounded-bl-sm shadow-sm border border-gray-100'
|
||||||
}`}>
|
}`}>
|
||||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">{m.content}</p>
|
<p className="text-sm leading-relaxed whitespace-pre-wrap">{m.content}</p>
|
||||||
{m.time && <p className={`text-[10px] mt-1 ${m.role === 'user' ? 'text-[#0a0a0f]/50' : 'text-gray-600'}`}>{m.time}</p>}
|
{m.time && <p className={`text-[10px] mt-1 ${m.role === 'user' ? 'text-white/60' : 'text-gray-400'}`}>{m.time}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
{(loading || dailyLoading) && (
|
{(loading || dailyLoading) && (
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<div className="w-7 h-7 rounded-xl bg-[#D4AF37]/10 flex items-center justify-center mr-2 shrink-0 mt-0.5">
|
<div className="w-7 h-7 rounded-xl bg-violet-50 flex items-center justify-center mr-2 shrink-0 mt-0.5">
|
||||||
<PersonaIcon size={13} className={PERSONA_COLORS[profile.coachPersona]} />
|
<PersonaIcon size={13} className={PERSONA_COLORS[profile.coachPersona]} />
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-[#1a1a26] rounded-2xl rounded-bl-sm px-4 py-3">
|
<div className="bg-white rounded-2xl rounded-bl-sm px-4 py-3 shadow-sm border border-gray-100">
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<div className="w-1.5 h-1.5 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
||||||
<div className="w-1.5 h-1.5 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
||||||
<div className="w-1.5 h-1.5 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,18 +276,18 @@ export default function NurPage() {
|
|||||||
|
|
||||||
{/* Limit reached banner */}
|
{/* Limit reached banner */}
|
||||||
{limitReached && (
|
{limitReached && (
|
||||||
<div className="shrink-0 mx-4 mb-2 bg-[#1a1a26] border border-[#D4AF37]/25 rounded-2xl px-4 py-3 flex items-center justify-between gap-3">
|
<div className="shrink-0 mx-4 mb-2 bg-amber-50 border border-amber-200 rounded-2xl px-4 py-3 flex items-center justify-between gap-3">
|
||||||
<p className="text-sm text-gray-300 text-xs">Used all {FREE_MSG_LIMIT} free messages today.</p>
|
<p className="text-xs text-gray-700">Used all {FREE_MSG_LIMIT} free messages today.</p>
|
||||||
<a href="/upgrade" className="shrink-0 bg-[#D4AF37] text-[#0a0a0f] px-3 py-1.5 rounded-xl text-xs font-bold">
|
<a href="/upgrade" className="shrink-0 bg-emerald-600 text-white px-3 py-1.5 rounded-xl text-xs font-bold">
|
||||||
Upgrade
|
Upgrade
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Input */}
|
{/* Input */}
|
||||||
<div className="shrink-0 border-t border-gray-800/60 px-4 pt-3 pb-3 bg-[#0a0a0f]">
|
<div className="shrink-0 border-t border-gray-100 px-4 pt-3 pb-3 bg-white">
|
||||||
{!isPremiumUser && dailyMsgCount !== null && !limitReached && (
|
{!isPremiumUser && dailyMsgCount !== null && !limitReached && (
|
||||||
<p className="text-[11px] text-gray-700 text-right mb-1.5">
|
<p className="text-[11px] text-gray-400 text-right mb-1.5">
|
||||||
{dailyMsgCount} / {FREE_MSG_LIMIT} today
|
{dailyMsgCount} / {FREE_MSG_LIMIT} today
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -299,14 +299,14 @@ export default function NurPage() {
|
|||||||
onChange={e => setInput(e.target.value)}
|
onChange={e => setInput(e.target.value)}
|
||||||
onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend() } }}
|
onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend() } }}
|
||||||
disabled={limitReached}
|
disabled={limitReached}
|
||||||
className="flex-1 bg-[#1a1a26] border border-gray-800 rounded-2xl px-4 py-3 text-sm text-white placeholder-gray-600 focus:border-[#D4AF37]/50 focus:outline-none disabled:opacity-40 transition"
|
className="flex-1 bg-gray-50 border border-gray-200 rounded-2xl px-4 py-3 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none disabled:opacity-40 transition"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={loading || !input.trim() || limitReached}
|
disabled={loading || !input.trim() || limitReached}
|
||||||
className="w-11 h-11 flex items-center justify-center bg-[#D4AF37] rounded-2xl disabled:opacity-30 active:scale-95 transition-all shrink-0"
|
className="w-11 h-11 flex items-center justify-center bg-emerald-600 rounded-2xl disabled:opacity-30 active:scale-95 transition-all shrink-0"
|
||||||
>
|
>
|
||||||
<Send size={18} className="text-[#0a0a0f]" />
|
<Send size={18} className="text-white" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -314,17 +314,17 @@ export default function NurPage() {
|
|||||||
{/* Settings Drawer (slide-up overlay) */}
|
{/* Settings Drawer (slide-up overlay) */}
|
||||||
{showSettings && (
|
{showSettings && (
|
||||||
<div className="fixed inset-0 z-50 flex flex-col justify-end">
|
<div className="fixed inset-0 z-50 flex flex-col justify-end">
|
||||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setShowSettings(false)} />
|
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" onClick={() => setShowSettings(false)} />
|
||||||
<div className="relative bg-[#111118] rounded-t-3xl border-t border-gray-800/60 max-h-[88dvh] overflow-y-auto">
|
<div className="relative bg-white rounded-t-3xl max-h-[88dvh] overflow-y-auto">
|
||||||
{/* Handle */}
|
{/* Handle */}
|
||||||
<div className="flex justify-center pt-3 pb-1">
|
<div className="flex justify-center pt-3 pb-1">
|
||||||
<div className="w-10 h-1 bg-gray-700 rounded-full" />
|
<div className="w-10 h-1 bg-gray-200 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-5 pb-8 space-y-6 pt-3">
|
<div className="px-5 pb-8 space-y-6 pt-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="font-bold text-white text-base">Your Spiritual Guide</h3>
|
<h3 className="font-bold text-gray-900 text-base">Your Spiritual Guide</h3>
|
||||||
<button onClick={() => setShowSettings(false)} className="w-8 h-8 flex items-center justify-center rounded-xl bg-gray-800 text-gray-400">
|
<button onClick={() => setShowSettings(false)} className="w-8 h-8 flex items-center justify-center rounded-xl bg-gray-100 text-gray-500">
|
||||||
<X size={16} />
|
<X size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -332,9 +332,9 @@ export default function NurPage() {
|
|||||||
{/* Scholar Selector */}
|
{/* Scholar Selector */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{!isPremiumUser && (
|
{!isPremiumUser && (
|
||||||
<div className="flex items-center gap-2 bg-[#D4AF37]/8 border border-[#D4AF37]/20 rounded-xl px-3 py-2.5">
|
<div className="flex items-center gap-2 bg-amber-50 border border-amber-200 rounded-xl px-3 py-2.5">
|
||||||
<Crown size={14} className="text-[#D4AF37] shrink-0" />
|
<Crown size={14} className="text-amber-500 shrink-0" />
|
||||||
<p className="text-xs text-[#D4AF37]">Upgrade to unlock scholar personas</p>
|
<p className="text-xs text-amber-700">Upgrade to unlock scholar personas</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
@@ -351,15 +351,15 @@ export default function NurPage() {
|
|||||||
setProfile(p => ({ ...p, coachPersona: personaId }))
|
setProfile(p => ({ ...p, coachPersona: personaId }))
|
||||||
}}
|
}}
|
||||||
className={`relative p-4 rounded-2xl border text-left transition active:scale-[0.97] ${
|
className={`relative p-4 rounded-2xl border text-left transition active:scale-[0.97] ${
|
||||||
isLocked ? 'border-gray-800 bg-[#0d0d14] opacity-50' :
|
isLocked ? 'border-gray-200 bg-gray-50 opacity-50' :
|
||||||
isActive ? 'border-[#D4AF37]/50 bg-[#D4AF37]/5' :
|
isActive ? 'border-emerald-400 bg-emerald-50' :
|
||||||
'border-gray-800 bg-[#0d0d14] active:bg-gray-800'
|
'border-gray-200 bg-gray-50 active:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isLocked && <Lock size={11} className="absolute top-3 right-3 text-gray-600" />}
|
{isLocked && <Lock size={11} className="absolute top-3 right-3 text-gray-400" />}
|
||||||
<Icon size={22} className={`mb-2 ${isActive && !isLocked ? PERSONA_COLORS[personaId] : 'text-gray-600'}`} />
|
<Icon size={22} className={`mb-2 ${isActive && !isLocked ? PERSONA_COLORS[personaId] : 'text-gray-400'}`} />
|
||||||
<p className={`text-sm font-semibold leading-tight ${isActive && !isLocked ? 'text-white' : 'text-gray-400'}`}>{persona.name}</p>
|
<p className={`text-sm font-semibold leading-tight ${isActive && !isLocked ? 'text-gray-900' : 'text-gray-500'}`}>{persona.name}</p>
|
||||||
<p className="text-[11px] text-gray-600 mt-0.5 leading-tight">{persona.title}</p>
|
<p className="text-[11px] text-gray-400 mt-0.5 leading-tight">{persona.title}</p>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -375,7 +375,7 @@ export default function NurPage() {
|
|||||||
value={profile.preferredName}
|
value={profile.preferredName}
|
||||||
onChange={e => setProfile(p => ({ ...p, preferredName: e.target.value }))}
|
onChange={e => setProfile(p => ({ ...p, preferredName: e.target.value }))}
|
||||||
placeholder="How your guide addresses you"
|
placeholder="How your guide addresses you"
|
||||||
className="w-full bg-[#0d0d14] border border-gray-800 rounded-xl px-4 py-3 text-sm text-white placeholder-gray-700 focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
@@ -383,7 +383,7 @@ export default function NurPage() {
|
|||||||
<select
|
<select
|
||||||
value={profile.experienceLevel}
|
value={profile.experienceLevel}
|
||||||
onChange={e => setProfile(p => ({ ...p, experienceLevel: e.target.value }))}
|
onChange={e => setProfile(p => ({ ...p, experienceLevel: e.target.value }))}
|
||||||
className="w-full bg-[#0d0d14] border border-gray-800 rounded-xl px-4 py-3 text-sm text-white focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-sm text-gray-900 focus:border-emerald-400 focus:outline-none"
|
||||||
>
|
>
|
||||||
<option value="new">New to Islam</option>
|
<option value="new">New to Islam</option>
|
||||||
<option value="growing">Growing in Faith</option>
|
<option value="growing">Growing in Faith</option>
|
||||||
@@ -395,7 +395,7 @@ export default function NurPage() {
|
|||||||
<select
|
<select
|
||||||
value={profile.madhab}
|
value={profile.madhab}
|
||||||
onChange={e => setProfile(p => ({ ...p, madhab: e.target.value }))}
|
onChange={e => setProfile(p => ({ ...p, madhab: e.target.value }))}
|
||||||
className="w-full bg-[#0d0d14] border border-gray-800 rounded-xl px-4 py-3 text-sm text-white focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-sm text-gray-900 focus:border-emerald-400 focus:outline-none"
|
||||||
>
|
>
|
||||||
<option value="unspecified">Madhab Neutral</option>
|
<option value="unspecified">Madhab Neutral</option>
|
||||||
<option value="hanafi">Hanafi</option>
|
<option value="hanafi">Hanafi</option>
|
||||||
@@ -410,12 +410,12 @@ export default function NurPage() {
|
|||||||
{actionItems.length > 0 && (
|
{actionItems.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Target size={13} className="text-[#D4AF37]" />
|
<Target size={13} className="text-emerald-600" />
|
||||||
<span className="text-xs font-semibold text-gray-400 uppercase tracking-widest">Your Commitments</span>
|
<span className="text-xs font-semibold text-gray-500 uppercase tracking-widest">Your Commitments</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{actionItems.map((item, i) => (
|
{actionItems.map((item, i) => (
|
||||||
<span key={i} className="text-xs px-3 py-1.5 rounded-xl bg-[#D4AF37]/5 text-gray-400 border border-[#D4AF37]/10">
|
<span key={i} className="text-xs px-3 py-1.5 rounded-xl bg-emerald-50 text-gray-600 border border-emerald-100">
|
||||||
{item.length > 50 ? item.slice(0, 50) + '…' : item}
|
{item.length > 50 ? item.slice(0, 50) + '…' : item}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@@ -426,7 +426,7 @@ export default function NurPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSaveProfile}
|
onClick={handleSaveProfile}
|
||||||
disabled={savingProfile}
|
disabled={savingProfile}
|
||||||
className="w-full bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold text-sm disabled:opacity-50 active:scale-[0.98] transition-all"
|
className="w-full bg-emerald-600 text-white py-4 rounded-2xl font-bold text-sm disabled:opacity-50 active:scale-[0.98] transition-all"
|
||||||
>
|
>
|
||||||
{savingProfile ? 'Saving...' : 'Save & Close'}
|
{savingProfile ? 'Saving...' : 'Save & Close'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { redirect } from 'next/navigation'
|
import { redirect } from 'next/navigation'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
redirect('/prayer')
|
redirect('/home')
|
||||||
}
|
}
|
||||||
|
|||||||
+141
-124
@@ -139,79 +139,48 @@ export default function PrayerPage() {
|
|||||||
const C = 2 * Math.PI * R
|
const C = 2 * Math.PI * R
|
||||||
const strokeOffset = C * (1 - arcPercent)
|
const strokeOffset = C * (1 - arcPercent)
|
||||||
|
|
||||||
const currentTime = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] flex flex-col pb-24 select-none">
|
<div className="min-h-dvh bg-[#f5f4f0] flex flex-col pb-24 select-none">
|
||||||
|
|
||||||
{/* Top bar */}
|
{/* Header gradient card */}
|
||||||
<div className="flex items-center justify-between px-5 pt-6 pb-2">
|
<div className="bg-gradient-to-br from-emerald-700 to-teal-600 rounded-b-3xl px-5 pt-10 pb-8">
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-1.5">
|
{/* Top bar inside header */}
|
||||||
{city ? (
|
<div className="flex items-center justify-between mb-6">
|
||||||
<>
|
<div>
|
||||||
<MapPin size={12} className="text-gray-600" />
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-xs text-gray-500">{city}</span>
|
{city ? (
|
||||||
</>
|
<>
|
||||||
) : (
|
<MapPin size={12} className="text-white/70" />
|
||||||
<span className="text-xs text-gray-700">Detecting location…</span>
|
<span className="text-xs text-white/80">{city}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-white/60">Detecting location…</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{hijri && (
|
||||||
|
<p className="text-[11px] text-white/60 mt-0.5">
|
||||||
|
{hijri.date} {hijri.month.en} {hijri.year} AH
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{hijri && (
|
|
||||||
<p className="text-[11px] text-[#D4AF37]/60 mt-0.5">
|
|
||||||
{hijri.date} {hijri.month.en} {hijri.year} AH
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={requestLocation}
|
|
||||||
disabled={loading}
|
|
||||||
className="w-9 h-9 rounded-xl bg-gray-800/60 flex items-center justify-center active:scale-90 transition-transform"
|
|
||||||
>
|
|
||||||
<RefreshCw size={15} className={`text-gray-400 ${loading ? 'animate-spin' : ''}`} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Location denied */}
|
|
||||||
{locationDenied && (
|
|
||||||
<div className="mx-5 mt-4 rounded-3xl bg-gray-900 border border-gray-800 p-8 text-center">
|
|
||||||
<div className="text-4xl mb-4">🕌</div>
|
|
||||||
<p className="text-white font-bold text-lg mb-1">Enable Location</p>
|
|
||||||
<p className="text-gray-500 text-sm mb-6">Prayer times require your location to be accurate</p>
|
|
||||||
<button
|
<button
|
||||||
onClick={requestLocation}
|
onClick={requestLocation}
|
||||||
className="w-full bg-[#D4AF37] text-[#0a0a0f] font-bold py-4 rounded-2xl active:scale-[0.98] transition-transform"
|
disabled={loading}
|
||||||
|
className="w-9 h-9 rounded-xl bg-white/10 flex items-center justify-center active:scale-[0.97] transition-transform"
|
||||||
>
|
>
|
||||||
Allow Location Access
|
<RefreshCw size={15} className={`text-white/80 ${loading ? 'animate-spin' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Error */}
|
{/* Next prayer arc + countdown inside header */}
|
||||||
{error && !locationDenied && (
|
{timings && next && countdown && (
|
||||||
<div className="mx-5 mt-4 bg-red-500/10 border border-red-500/20 rounded-2xl px-4 py-3 text-center">
|
<div className="flex flex-col items-center">
|
||||||
<p className="text-red-400 text-sm">{error}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Loading state */}
|
|
||||||
{loading && !timings && (
|
|
||||||
<div className="flex-1 flex flex-col items-center justify-center gap-4 pt-8">
|
|
||||||
<div className="w-10 h-10 rounded-full border-2 border-[#D4AF37]/20 border-t-[#D4AF37] animate-spin" />
|
|
||||||
<p className="text-xs text-gray-600">Fetching prayer times…</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
{timings && next && countdown && (
|
|
||||||
<>
|
|
||||||
{/* Hero — next prayer */}
|
|
||||||
<div className="flex flex-col items-center pt-6 pb-8 px-5">
|
|
||||||
{/* Arc clock */}
|
{/* Arc clock */}
|
||||||
<div className="relative flex items-center justify-center mb-6">
|
<div className="relative flex items-center justify-center mb-5">
|
||||||
<svg width="160" height="160" className="-rotate-90">
|
<svg width="160" height="160" className="-rotate-90">
|
||||||
{/* Background ring */}
|
{/* Background ring */}
|
||||||
<circle cx="80" cy="80" r={R} fill="none" stroke="#1c1c2a" strokeWidth="6" />
|
<circle cx="80" cy="80" r={R} fill="none" stroke="rgba(255,255,255,0.15)" strokeWidth="6" />
|
||||||
{/* Progress arc */}
|
{/* Progress arc */}
|
||||||
<circle
|
<circle
|
||||||
cx="80" cy="80" r={R} fill="none"
|
cx="80" cy="80" r={R} fill="none"
|
||||||
@@ -222,10 +191,10 @@ export default function PrayerPage() {
|
|||||||
strokeDashoffset={strokeOffset}
|
strokeDashoffset={strokeOffset}
|
||||||
className="transition-all duration-1000"
|
className="transition-all duration-1000"
|
||||||
/>
|
/>
|
||||||
{/* Glow dots at end of arc */}
|
{/* Glow dot at end of arc */}
|
||||||
<circle
|
<circle
|
||||||
cx="80" cy="80" r={R} fill="none"
|
cx="80" cy="80" r={R} fill="none"
|
||||||
stroke="#D4AF37"
|
stroke="rgba(255,255,255,0.4)"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeDasharray="1"
|
strokeDasharray="1"
|
||||||
@@ -236,83 +205,131 @@ export default function PrayerPage() {
|
|||||||
{/* Center content */}
|
{/* Center content */}
|
||||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
<span className="text-3xl mb-0.5">{PRAYER_DATA[next.name].emoji}</span>
|
<span className="text-3xl mb-0.5">{PRAYER_DATA[next.name].emoji}</span>
|
||||||
<span className="text-[10px] text-gray-600 uppercase tracking-widest">Next Prayer</span>
|
<span className="text-[10px] text-white/60 uppercase tracking-widest">Next Prayer</span>
|
||||||
<span className="text-base font-bold text-white mt-0.5">{next.name}</span>
|
<span className="text-base font-bold text-white mt-0.5">{next.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Countdown */}
|
{/* Countdown */}
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-5xl font-bold text-[#D4AF37] tabular-nums leading-none tracking-tight">
|
<p className="text-5xl font-bold text-white tabular-nums leading-none tracking-tight">
|
||||||
{countdown.value}
|
{countdown.value}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-500 mt-1.5">{countdown.unit}</p>
|
<p className="text-sm text-white/60 mt-1.5">{countdown.unit}</p>
|
||||||
<p className="text-xs text-gray-700 mt-1">
|
<p className="text-xs text-white/50 mt-1">
|
||||||
{next.name} at {fmt12(timings[next.name])}
|
{next.name} at {fmt12(timings[next.name])}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Loading skeleton inside header */}
|
||||||
<div className="mx-5 h-px bg-gray-800/60 mb-5" />
|
{loading && !timings && (
|
||||||
|
<div className="flex flex-col items-center gap-3 py-4">
|
||||||
{/* Prayer list */}
|
<div className="w-40 h-40 rounded-full bg-white/10 animate-pulse" />
|
||||||
<div className="px-4 space-y-1.5">
|
<div className="w-24 h-8 rounded-xl bg-white/10 animate-pulse mt-2" />
|
||||||
{PRAYERS.map((prayer, i) => {
|
<div className="w-32 h-4 rounded-lg bg-white/10 animate-pulse" />
|
||||||
const pMins = toMins(timings[prayer])
|
|
||||||
const isNext = prayer === next.name
|
|
||||||
const isPast = pMins < nowMins && !isNext
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={prayer}
|
|
||||||
className={`flex items-center gap-3 px-4 py-3.5 rounded-2xl transition-all ${
|
|
||||||
isNext
|
|
||||||
? 'bg-[#D4AF37]/10 border border-[#D4AF37]/25'
|
|
||||||
: isPast
|
|
||||||
? 'opacity-35'
|
|
||||||
: 'bg-gray-900/50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* Emoji */}
|
|
||||||
<span className="text-xl w-7 text-center">{PRAYER_DATA[prayer].emoji}</span>
|
|
||||||
|
|
||||||
{/* Names */}
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className={`font-semibold text-sm ${isNext ? 'text-[#D4AF37]' : isPast ? 'text-gray-600' : 'text-white'}`}>
|
|
||||||
{prayer}
|
|
||||||
</span>
|
|
||||||
{isNext && (
|
|
||||||
<span className="text-[10px] bg-[#D4AF37]/20 text-[#D4AF37] px-1.5 py-0.5 rounded-full font-medium">
|
|
||||||
Next
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<span className="text-[11px] text-gray-700">{PRAYER_DATA[prayer].arabic}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Time */}
|
|
||||||
<span className={`text-sm font-bold tabular-nums ${isNext ? 'text-[#D4AF37]' : isPast ? 'text-gray-700' : 'text-gray-300'}`}>
|
|
||||||
{fmt12(timings[prayer])}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* Sunrise */}
|
|
||||||
<div className="flex items-center gap-3 px-4 py-2.5 opacity-25">
|
|
||||||
<span className="text-lg w-7 text-center">🌄</span>
|
|
||||||
<span className="flex-1 text-xs text-gray-600">Sunrise</span>
|
|
||||||
<span className="text-xs text-gray-600 tabular-nums">{fmt12(timings.Sunrise)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Footer note */}
|
{/* Location denied */}
|
||||||
<p className="text-center text-[10px] text-gray-800 mt-8 mb-2">
|
{locationDenied && (
|
||||||
Muslim World League (MWL) · AlAdhan
|
<div className="mx-5 mt-5 bg-white rounded-2xl shadow-sm border border-gray-100 p-8 text-center">
|
||||||
</p>
|
<div className="text-4xl mb-4">🕌</div>
|
||||||
</>
|
<p className="text-gray-900 font-bold text-lg mb-1">Enable Location</p>
|
||||||
|
<p className="text-gray-500 text-sm mb-6">Prayer times require your location to be accurate</p>
|
||||||
|
<button
|
||||||
|
onClick={requestLocation}
|
||||||
|
className="w-full bg-gradient-to-r from-emerald-700 to-teal-600 text-white font-bold py-4 rounded-2xl active:scale-[0.97] transition-transform"
|
||||||
|
>
|
||||||
|
Allow Location Access
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error */}
|
||||||
|
{error && !locationDenied && (
|
||||||
|
<div className="mx-5 mt-4 bg-red-50 border border-red-100 rounded-2xl px-4 py-3 text-center">
|
||||||
|
<p className="text-red-500 text-sm">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Prayer list */}
|
||||||
|
{timings && next && (
|
||||||
|
<div className="px-4 mt-5 space-y-2">
|
||||||
|
{PRAYERS.map((prayer) => {
|
||||||
|
const pMins = toMins(timings[prayer])
|
||||||
|
const isNext = prayer === next.name
|
||||||
|
const isPast = pMins < nowMins && !isNext
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={prayer}
|
||||||
|
className={`flex items-center gap-3 px-4 py-3.5 rounded-2xl transition-all ${
|
||||||
|
isNext
|
||||||
|
? 'bg-amber-50 border border-[#D4AF37]'
|
||||||
|
: isPast
|
||||||
|
? 'bg-white border border-gray-100 shadow-sm opacity-50'
|
||||||
|
: 'bg-white border border-gray-100 shadow-sm'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* Emoji */}
|
||||||
|
<span className="text-xl w-7 text-center">{PRAYER_DATA[prayer].emoji}</span>
|
||||||
|
|
||||||
|
{/* Names */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className={`font-semibold text-sm ${isNext ? 'text-emerald-700' : 'text-gray-900'}`}>
|
||||||
|
{prayer}
|
||||||
|
</span>
|
||||||
|
{isNext && (
|
||||||
|
<span className="text-[10px] bg-[#D4AF37]/20 text-[#D4AF37] px-1.5 py-0.5 rounded-full font-medium">
|
||||||
|
Next
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="text-[11px] text-gray-500">{PRAYER_DATA[prayer].arabic}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Time */}
|
||||||
|
<span className={`text-sm font-bold tabular-nums ${isNext ? 'text-[#D4AF37]' : 'text-gray-500'}`}>
|
||||||
|
{fmt12(timings[prayer])}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Sunrise */}
|
||||||
|
<div className="flex items-center gap-3 px-4 py-2.5 bg-white rounded-2xl border border-gray-100 shadow-sm opacity-40">
|
||||||
|
<span className="text-lg w-7 text-center">🌄</span>
|
||||||
|
<span className="flex-1 text-xs text-gray-500">Sunrise</span>
|
||||||
|
<span className="text-xs text-gray-500 tabular-nums">{fmt12(timings.Sunrise)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Loading skeleton for prayer list */}
|
||||||
|
{loading && !timings && (
|
||||||
|
<div className="px-4 mt-5 space-y-2">
|
||||||
|
{[...Array(5)].map((_, i) => (
|
||||||
|
<div key={i} className="bg-white rounded-2xl shadow-sm border border-gray-100 px-4 py-3.5 flex items-center gap-3">
|
||||||
|
<div className="w-7 h-7 rounded-full bg-gray-200 animate-pulse" />
|
||||||
|
<div className="flex-1 space-y-1.5">
|
||||||
|
<div className="h-3.5 w-20 rounded bg-gray-200 animate-pulse" />
|
||||||
|
<div className="h-2.5 w-12 rounded bg-gray-200 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<div className="h-3.5 w-16 rounded bg-gray-200 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Footer note */}
|
||||||
|
{timings && (
|
||||||
|
<p className="text-center text-[10px] text-gray-400 mt-8 mb-2">
|
||||||
|
Muslim World League (MWL) · AlAdhan
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+60
-49
@@ -56,63 +56,69 @@ export default function ProfilePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tier = user?.isPro ? 'Pro' : user?.isPremium ? 'Premium' : 'Free'
|
const tier = user?.isPro ? 'Pro' : user?.isPremium ? 'Premium' : 'Free'
|
||||||
const tierColor = user?.isPro ? 'text-purple-400' : user?.isPremium ? 'text-[#D4AF37]' : 'text-gray-500'
|
const tierColor = user?.isPro ? 'text-purple-600' : user?.isPremium ? 'text-[#D4AF37]' : 'text-gray-500'
|
||||||
const tierBg = user?.isPro ? 'bg-purple-400/10 border-purple-400/20' : user?.isPremium ? 'bg-[#D4AF37]/10 border-[#D4AF37]/20' : 'bg-gray-800/50 border-gray-700/50'
|
const tierBg = user?.isPro ? 'bg-purple-50 border-purple-200' : user?.isPremium ? 'bg-amber-50 border-amber-200' : 'bg-gray-100 border-gray-200'
|
||||||
|
|
||||||
if (authLoading) return <ProfileSkeleton />
|
if (authLoading) return <ProfileSkeleton />
|
||||||
if (!token) return null
|
if (!token) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] pb-24">
|
<div className="min-h-dvh bg-[#f5f4f0] pb-24">
|
||||||
|
|
||||||
{/* Header */}
|
{/* Avatar / Hero Card */}
|
||||||
<div className="px-5 pt-6 pb-5">
|
<div className="bg-gradient-to-br from-emerald-700 to-teal-600 px-5 pt-12 pb-8 mb-4">
|
||||||
<h1 className="text-xl font-bold text-white">Profile</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Avatar + info card */}
|
|
||||||
<div className="mx-4 bg-[#111118] border border-gray-800/60 rounded-3xl p-5 mb-4">
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-16 h-16 rounded-2xl bg-[#D4AF37]/10 border border-[#D4AF37]/20 flex items-center justify-center shrink-0">
|
{/* Avatar circle */}
|
||||||
<User size={28} className="text-[#D4AF37]" />
|
<div className="w-16 h-16 rounded-full bg-white/20 border-2 border-white flex items-center justify-center shrink-0">
|
||||||
|
<User size={28} className="text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h2 className="text-lg font-bold text-white truncate">{user?.name || 'User'}</h2>
|
<h2 className="text-lg font-bold text-white truncate">{user?.name || 'User'}</h2>
|
||||||
<p className="text-sm text-gray-500 truncate">{user?.email || ''}</p>
|
<p className="text-sm text-white/70 truncate">{user?.email || ''}</p>
|
||||||
|
{/* Tier badge */}
|
||||||
<div className={`inline-flex items-center gap-1.5 mt-2 border rounded-full px-2.5 py-1 ${tierBg}`}>
|
<div className={`inline-flex items-center gap-1.5 mt-2 border rounded-full px-2.5 py-1 ${tierBg}`}>
|
||||||
<Star size={11} className={tierColor} />
|
<Star size={11} className={tierColor} />
|
||||||
<span className={`text-xs font-semibold ${tierColor}`}>{tier}</span>
|
<span className={`text-xs font-semibold ${tierColor}`}>{tier}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Gold premium badge for paid users */}
|
||||||
|
{(user?.isPremium || user?.isPro) && (
|
||||||
|
<div className="shrink-0 bg-gradient-to-br from-[#D4AF37] to-amber-500 rounded-xl px-3 py-1.5">
|
||||||
|
<Crown size={14} className="text-white" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="grid grid-cols-2 gap-3 mx-4 mb-4">
|
<div className="grid grid-cols-2 gap-3 mx-4 mb-4">
|
||||||
<div className="bg-[#111118] border border-gray-800/60 rounded-2xl p-4">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||||
<Wallet size={20} className="text-[#D4AF37] mb-2" />
|
<Wallet size={20} className="text-[#D4AF37] mb-2" />
|
||||||
<p className="text-2xl font-bold text-white">{user?.flhBalance?.toLocaleString() || 0}</p>
|
<p className="text-2xl font-bold text-gray-900">{user?.flhBalance?.toLocaleString() || 0}</p>
|
||||||
<p className="text-xs text-gray-600 mt-0.5">FLH Balance</p>
|
<p className="text-xs text-gray-500 mt-0.5">FLH Balance</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-[#111118] border border-gray-800/60 rounded-2xl p-4">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||||
<ShoppingBag size={20} className="text-blue-400 mb-2" />
|
<ShoppingBag size={20} className="text-emerald-500 mb-2" />
|
||||||
<p className="text-2xl font-bold text-white">0</p>
|
<p className="text-2xl font-bold text-gray-900">0</p>
|
||||||
<p className="text-xs text-gray-600 mt-0.5">Purchases</p>
|
<p className="text-xs text-gray-500 mt-0.5">Purchases</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upgrade CTA — only for free users */}
|
{/* Upgrade CTA — only for free users */}
|
||||||
{!user?.isPremium && !user?.isPro && (
|
{!user?.isPremium && !user?.isPro && (
|
||||||
<div className="mx-4 mb-4">
|
<div className="mx-4 mb-4">
|
||||||
<Link href="/upgrade" className="block bg-gradient-to-r from-[#D4AF37]/15 to-[#D4AF37]/5 border border-[#D4AF37]/25 rounded-3xl p-5">
|
<Link
|
||||||
|
href="/upgrade"
|
||||||
|
className="block bg-gradient-to-r from-amber-50 to-yellow-50 border border-[#D4AF37]/30 rounded-3xl p-5 active:scale-[0.97] transition-all"
|
||||||
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 rounded-2xl bg-[#D4AF37]/15 flex items-center justify-center">
|
<div className="w-10 h-10 rounded-2xl bg-[#D4AF37]/15 flex items-center justify-center">
|
||||||
<Crown size={20} className="text-[#D4AF37]" />
|
<Crown size={20} className="text-[#D4AF37]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-bold text-[#D4AF37] text-sm">Go Premium</p>
|
<p className="font-bold text-gray-900 text-sm">Go Premium</p>
|
||||||
<p className="text-xs text-gray-500">Unlock scholars & unlimited chat</p>
|
<p className="text-xs text-gray-500">Unlock scholars & unlimited chat</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight size={18} className="text-[#D4AF37]" />
|
<ChevronRight size={18} className="text-[#D4AF37]" />
|
||||||
@@ -122,29 +128,29 @@ export default function ProfilePage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Edit Profile Form */}
|
{/* Edit Profile Form */}
|
||||||
<div className="mx-4 bg-[#111118] border border-gray-800/60 rounded-3xl p-5 mb-4 space-y-4">
|
<div className="mx-4 bg-white rounded-2xl shadow-sm border border-gray-100 p-5 mb-4 space-y-4">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<Shield size={15} className="text-[#D4AF37]" />
|
<Shield size={15} className="text-emerald-600" />
|
||||||
<h2 className="font-bold text-white text-sm">Edit Profile</h2>
|
<h2 className="font-bold text-gray-900 text-sm">Edit Profile</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-xs font-semibold text-gray-600 uppercase tracking-widest">Preferred Name</label>
|
<label className="text-xs font-semibold text-gray-500 uppercase tracking-widest">Preferred Name</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={profile.preferredName}
|
value={profile.preferredName}
|
||||||
onChange={e => setProfile(p => ({ ...p, preferredName: e.target.value }))}
|
onChange={e => setProfile(p => ({ ...p, preferredName: e.target.value }))}
|
||||||
placeholder="How Nur addresses you"
|
placeholder="How Nur addresses you"
|
||||||
className="w-full bg-[#0d0d14] border border-gray-800 rounded-xl px-4 py-3 text-sm text-white placeholder-gray-700 focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-[#f5f4f0] border border-gray-200 rounded-xl px-4 py-3 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none transition"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-xs font-semibold text-gray-600 uppercase tracking-widest">Experience Level</label>
|
<label className="text-xs font-semibold text-gray-500 uppercase tracking-widest">Experience Level</label>
|
||||||
<select
|
<select
|
||||||
value={profile.experienceLevel}
|
value={profile.experienceLevel}
|
||||||
onChange={e => setProfile(p => ({ ...p, experienceLevel: e.target.value }))}
|
onChange={e => setProfile(p => ({ ...p, experienceLevel: e.target.value }))}
|
||||||
className="w-full bg-[#0d0d14] border border-gray-800 rounded-xl px-4 py-3 text-sm text-white focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-[#f5f4f0] border border-gray-200 rounded-xl px-4 py-3 text-sm text-gray-900 focus:border-emerald-400 focus:outline-none transition"
|
||||||
>
|
>
|
||||||
<option value="new">New to Islam</option>
|
<option value="new">New to Islam</option>
|
||||||
<option value="growing">Growing in Faith</option>
|
<option value="growing">Growing in Faith</option>
|
||||||
@@ -153,11 +159,11 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-xs font-semibold text-gray-600 uppercase tracking-widest">Madhab</label>
|
<label className="text-xs font-semibold text-gray-500 uppercase tracking-widest">Madhab</label>
|
||||||
<select
|
<select
|
||||||
value={profile.madhab}
|
value={profile.madhab}
|
||||||
onChange={e => setProfile(p => ({ ...p, madhab: e.target.value }))}
|
onChange={e => setProfile(p => ({ ...p, madhab: e.target.value }))}
|
||||||
className="w-full bg-[#0d0d14] border border-gray-800 rounded-xl px-4 py-3 text-sm text-white focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-[#f5f4f0] border border-gray-200 rounded-xl px-4 py-3 text-sm text-gray-900 focus:border-emerald-400 focus:outline-none transition"
|
||||||
>
|
>
|
||||||
<option value="unspecified">Madhab Neutral</option>
|
<option value="unspecified">Madhab Neutral</option>
|
||||||
<option value="hanafi">Hanafi</option>
|
<option value="hanafi">Hanafi</option>
|
||||||
@@ -170,7 +176,7 @@ export default function ProfilePage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="w-full flex items-center justify-center gap-2 bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-xl font-bold text-sm disabled:opacity-50 active:scale-[0.98] transition-all"
|
className="w-full flex items-center justify-center gap-2 bg-emerald-600 text-white py-4 rounded-xl font-bold text-sm disabled:opacity-50 active:scale-[0.97] transition-all"
|
||||||
>
|
>
|
||||||
<Save size={16} /> {saving ? 'Saving...' : 'Save Changes'}
|
<Save size={16} /> {saving ? 'Saving...' : 'Save Changes'}
|
||||||
</button>
|
</button>
|
||||||
@@ -178,15 +184,18 @@ export default function ProfilePage() {
|
|||||||
|
|
||||||
{/* Halal Monitor link */}
|
{/* Halal Monitor link */}
|
||||||
<div className="mx-4 mb-4">
|
<div className="mx-4 mb-4">
|
||||||
<Link href="/halal-monitor" className="flex items-center gap-3 bg-[#111118] border border-gray-800/60 rounded-2xl px-5 py-4">
|
<Link
|
||||||
<div className="w-9 h-9 rounded-xl bg-emerald-500/10 flex items-center justify-center">
|
href="/halal-monitor"
|
||||||
<Sparkles size={17} className="text-emerald-400" />
|
className="flex items-center gap-3 bg-white rounded-2xl shadow-sm border border-gray-100 px-5 py-4 active:scale-[0.97] transition-all"
|
||||||
|
>
|
||||||
|
<div className="w-9 h-9 rounded-xl bg-emerald-50 flex items-center justify-center">
|
||||||
|
<Sparkles size={17} className="text-emerald-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm font-semibold text-white">Halal Monitor</p>
|
<p className="text-sm font-semibold text-gray-900">Halal Monitor</p>
|
||||||
<p className="text-xs text-gray-600">Find mosques & halal food near you</p>
|
<p className="text-xs text-gray-500">Find mosques & halal food near you</p>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight size={16} className="text-gray-600" />
|
<ChevronRight size={16} className="text-gray-400" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -200,7 +209,7 @@ export default function ProfilePage() {
|
|||||||
}
|
}
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
}}
|
}}
|
||||||
className="w-full flex items-center justify-center gap-2 border border-red-900/50 text-red-500 bg-red-500/5 rounded-2xl py-4 text-sm font-semibold active:bg-red-500/10 transition"
|
className="w-full flex items-center justify-center gap-2 border border-red-200 text-red-500 bg-red-50 rounded-2xl py-4 text-sm font-semibold active:scale-[0.97] transition-all"
|
||||||
>
|
>
|
||||||
<LogOut size={16} /> Sign Out
|
<LogOut size={16} /> Sign Out
|
||||||
</button>
|
</button>
|
||||||
@@ -211,22 +220,24 @@ export default function ProfilePage() {
|
|||||||
|
|
||||||
function ProfileSkeleton() {
|
function ProfileSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] pb-24 animate-pulse">
|
<div className="min-h-dvh bg-[#f5f4f0] pb-24 animate-pulse">
|
||||||
<div className="px-5 pt-6 pb-5"><div className="h-7 w-20 bg-gray-800 rounded-xl" /></div>
|
{/* Hero skeleton */}
|
||||||
<div className="mx-4 bg-[#111118] border border-gray-800/60 rounded-3xl p-5 mb-4">
|
<div className="bg-gradient-to-br from-emerald-700 to-teal-600 px-5 pt-12 pb-8 mb-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-16 h-16 rounded-2xl bg-gray-800" />
|
<div className="w-16 h-16 rounded-full bg-white/30" />
|
||||||
<div className="space-y-2 flex-1">
|
<div className="space-y-2 flex-1">
|
||||||
<div className="h-5 w-32 bg-gray-800 rounded-lg" />
|
<div className="h-5 w-32 bg-white/30 rounded-lg" />
|
||||||
<div className="h-4 w-48 bg-gray-800 rounded-lg" />
|
<div className="h-4 w-48 bg-white/20 rounded-lg" />
|
||||||
<div className="h-6 w-20 bg-gray-800 rounded-full" />
|
<div className="h-6 w-20 bg-white/20 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-3 mx-4 mb-4">
|
<div className="grid grid-cols-2 gap-3 mx-4 mb-4">
|
||||||
{[...Array(2)].map((_, i) => <div key={i} className="bg-[#111118] border border-gray-800/60 rounded-2xl p-4 h-24" />)}
|
{[...Array(2)].map((_, i) => (
|
||||||
|
<div key={i} className="bg-white rounded-2xl border border-gray-100 p-4 h-24" />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-4 bg-[#111118] border border-gray-800/60 rounded-3xl p-5 h-64" />
|
<div className="mx-4 bg-white rounded-2xl border border-gray-100 p-5 h-64" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-16
@@ -31,17 +31,17 @@ export default function RegisterPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh flex flex-col bg-[#0a0a0f] px-6">
|
<div className="min-h-dvh flex flex-col bg-white px-6">
|
||||||
{/* Brand */}
|
{/* Brand */}
|
||||||
<div className="flex flex-col items-center pt-12 pb-8">
|
<div className="flex flex-col items-center pt-12 pb-8">
|
||||||
<div className="w-20 h-20 rounded-3xl bg-[#D4AF37]/10 border border-[#D4AF37]/25 flex items-center justify-center mb-5">
|
<div className="w-20 h-20 rounded-3xl bg-gradient-to-br from-emerald-600 to-teal-500 flex items-center justify-center mb-5 shadow-lg shadow-emerald-200">
|
||||||
<span className="text-3xl font-bold text-[#D4AF37]">ف</span>
|
<span className="text-3xl font-bold text-white">ف</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold text-white tracking-tight">Join Falah</h1>
|
<h1 className="text-2xl font-bold text-gray-900 tracking-tight">Join Falah</h1>
|
||||||
<p className="text-gray-500 text-sm mt-1">Your Islamic lifestyle companion</p>
|
<p className="text-gray-500 text-sm mt-1">Your Islamic lifestyle companion</p>
|
||||||
<div className="flex items-center gap-1.5 mt-4 bg-[#D4AF37]/8 border border-[#D4AF37]/20 rounded-full px-3.5 py-2">
|
<div className="flex items-center gap-1.5 mt-4 bg-emerald-50 border border-emerald-200 rounded-full px-3.5 py-2">
|
||||||
<Sparkles size={13} className="text-[#D4AF37]" />
|
<Sparkles size={13} className="text-emerald-600" />
|
||||||
<span className="text-xs text-[#D4AF37] font-semibold">7-day Premium trial — free</span>
|
<span className="text-xs text-emerald-700 font-semibold">7-day Premium trial — free</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export default function RegisterPage() {
|
|||||||
onChange={e => setName(e.target.value)}
|
onChange={e => setName(e.target.value)}
|
||||||
required
|
required
|
||||||
autoComplete="name"
|
autoComplete="name"
|
||||||
className="w-full bg-[#111118] border border-gray-800 rounded-2xl px-4 py-4 text-white placeholder-gray-700 focus:border-[#D4AF37]/60 focus:outline-none transition"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none transition"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ export default function RegisterPage() {
|
|||||||
required
|
required
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
inputMode="email"
|
inputMode="email"
|
||||||
className="w-full bg-[#111118] border border-gray-800 rounded-2xl px-4 py-4 text-white placeholder-gray-700 focus:border-[#D4AF37]/60 focus:outline-none transition"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none transition"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,12 +85,12 @@ export default function RegisterPage() {
|
|||||||
required
|
required
|
||||||
minLength={8}
|
minLength={8}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
className="w-full bg-[#111118] border border-gray-800 rounded-2xl px-4 py-4 pr-14 text-white placeholder-gray-700 focus:border-[#D4AF37]/60 focus:outline-none transition"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 pr-14 text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none transition"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPass(v => !v)}
|
onClick={() => setShowPass(v => !v)}
|
||||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-600 p-1"
|
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 p-1"
|
||||||
>
|
>
|
||||||
{showPass ? <EyeOff size={20} /> : <Eye size={20} />}
|
{showPass ? <EyeOff size={20} /> : <Eye size={20} />}
|
||||||
</button>
|
</button>
|
||||||
@@ -98,15 +98,15 @@ export default function RegisterPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-red-500/10 border border-red-500/20 rounded-2xl px-4 py-3">
|
<div className="bg-red-50 border border-red-200 rounded-xl px-4 py-3">
|
||||||
<p className="text-red-400 text-sm">{error}</p>
|
<p className="text-red-500 text-sm">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="w-full bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold text-base disabled:opacity-50 active:scale-[0.98] transition-all mt-1"
|
className="w-full bg-emerald-600 text-white py-4 rounded-2xl font-bold text-base disabled:opacity-50 active:scale-[0.97] transition-all mt-1 shadow-sm shadow-emerald-200"
|
||||||
>
|
>
|
||||||
{loading ? 'Creating account...' : 'Create Free Account'}
|
{loading ? 'Creating account...' : 'Create Free Account'}
|
||||||
</button>
|
</button>
|
||||||
@@ -115,11 +115,11 @@ export default function RegisterPage() {
|
|||||||
<div className="py-8 space-y-3 text-center">
|
<div className="py-8 space-y-3 text-center">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<Link href="/login" className="text-[#D4AF37] font-semibold">
|
<Link href="/login" className="text-emerald-600 font-semibold">
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-800">بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ</p>
|
<p className="text-xs text-gray-300">بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+45
-45
@@ -61,19 +61,19 @@ export default function SouqPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] pb-24 select-none">
|
<div className="min-h-dvh bg-[#f5f4f0] pb-24 select-none">
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="px-5 pt-6 pb-4">
|
<div className="px-5 pt-8 pb-4 bg-white">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold text-white">Souq</h1>
|
<h1 className="text-xl font-bold text-gray-900">Souq</h1>
|
||||||
<p className="text-[11px] text-gray-600 mt-0.5">Islamic digital marketplace</p>
|
<p className="text-[11px] text-gray-400 mt-0.5">Islamic digital marketplace</p>
|
||||||
</div>
|
</div>
|
||||||
{token && (
|
{token && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCreate(true)}
|
onClick={() => setShowCreate(true)}
|
||||||
className="flex items-center gap-1.5 bg-[#D4AF37] text-[#0a0a0f] px-4 py-2.5 rounded-xl font-bold text-sm active:scale-[0.96] transition-transform"
|
className="flex items-center gap-1.5 bg-emerald-600 text-white px-4 py-2.5 rounded-xl font-bold text-sm active:scale-[0.97] transition-transform"
|
||||||
>
|
>
|
||||||
<Plus size={16} /> Sell
|
<Plus size={16} /> Sell
|
||||||
</button>
|
</button>
|
||||||
@@ -82,27 +82,27 @@ export default function SouqPage() {
|
|||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search size={16} className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-600" />
|
<Search size={16} className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search products…"
|
placeholder="Search products…"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={e => setSearch(e.target.value)}
|
onChange={e => setSearch(e.target.value)}
|
||||||
className="w-full bg-[#111118] border border-gray-800 rounded-2xl pl-11 pr-4 py-3.5 text-sm text-white placeholder-gray-600 focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl pl-11 pr-4 py-4 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Category chips */}
|
{/* Category chips */}
|
||||||
<div className="flex gap-2 px-5 pb-4 overflow-x-auto scrollbar-none" style={{ scrollbarWidth: 'none' }}>
|
<div className="flex gap-2 px-5 py-4 overflow-x-auto scrollbar-none bg-white border-b border-gray-100" style={{ scrollbarWidth: 'none' }}>
|
||||||
{CATEGORIES.map(c => (
|
{CATEGORIES.map(c => (
|
||||||
<button
|
<button
|
||||||
key={c}
|
key={c}
|
||||||
onClick={() => setCategory(c)}
|
onClick={() => setCategory(c)}
|
||||||
className={`flex items-center gap-1.5 px-3.5 py-2 rounded-xl text-xs font-semibold whitespace-nowrap transition-all active:scale-95 ${
|
className={`flex items-center gap-1.5 px-3.5 py-2 rounded-xl text-xs font-semibold whitespace-nowrap transition-all active:scale-95 ${
|
||||||
category === c
|
category === c
|
||||||
? 'bg-[#D4AF37] text-[#0a0a0f]'
|
? 'bg-emerald-600 text-white'
|
||||||
: 'bg-gray-900 text-gray-500 border border-gray-800'
|
: 'bg-white text-gray-500 border border-gray-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{CATEGORY_ICONS[c]}</span>
|
<span>{CATEGORY_ICONS[c]}</span>
|
||||||
@@ -113,9 +113,9 @@ export default function SouqPage() {
|
|||||||
|
|
||||||
{/* Loading */}
|
{/* Loading */}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="px-5 space-y-3">
|
<div className="px-5 pt-4 space-y-3">
|
||||||
{[...Array(4)].map((_, i) => (
|
{[...Array(4)].map((_, i) => (
|
||||||
<div key={i} className="h-28 rounded-2xl bg-gray-900/60 animate-pulse" />
|
<div key={i} className="h-28 rounded-2xl bg-gray-200 animate-pulse" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -124,37 +124,37 @@ export default function SouqPage() {
|
|||||||
{!loading && filtered.length === 0 && (
|
{!loading && filtered.length === 0 && (
|
||||||
<div className="flex flex-col items-center justify-center pt-20 px-6 text-center">
|
<div className="flex flex-col items-center justify-center pt-20 px-6 text-center">
|
||||||
<div className="text-5xl mb-4">🏪</div>
|
<div className="text-5xl mb-4">🏪</div>
|
||||||
<p className="text-white font-semibold mb-1">Nothing here yet</p>
|
<p className="text-gray-900 font-semibold mb-1">Nothing here yet</p>
|
||||||
<p className="text-gray-600 text-sm">Be the first to list a product</p>
|
<p className="text-gray-400 text-sm">Be the first to list a product</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Listings */}
|
{/* Listings */}
|
||||||
{!loading && filtered.length > 0 && (
|
{!loading && filtered.length > 0 && (
|
||||||
<div className="px-5 space-y-3">
|
<div className="px-5 pt-4 space-y-3">
|
||||||
{filtered.map(l => (
|
{filtered.map(l => (
|
||||||
<button
|
<button
|
||||||
key={l.id}
|
key={l.id}
|
||||||
onClick={() => setSelectedListing(l)}
|
onClick={() => setSelectedListing(l)}
|
||||||
className="w-full text-left bg-[#111118] border border-gray-800/60 rounded-2xl p-4 active:scale-[0.99] transition-transform"
|
className="w-full text-left bg-white rounded-2xl shadow-sm border border-gray-100 p-4 active:scale-[0.97] transition-transform"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
{/* Icon block */}
|
{/* Icon block */}
|
||||||
<div className="w-12 h-12 rounded-xl bg-gray-800/60 flex items-center justify-center shrink-0 text-xl">
|
<div className="w-12 h-12 rounded-xl bg-gray-100 flex items-center justify-center shrink-0 text-xl">
|
||||||
{CATEGORY_ICONS[l.category] || '📦'}
|
{CATEGORY_ICONS[l.category] || '📦'}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<h3 className="font-semibold text-white text-sm leading-tight line-clamp-1">{l.title}</h3>
|
<h3 className="font-semibold text-gray-900 text-sm leading-tight line-clamp-1">{l.title}</h3>
|
||||||
{l.featured && <Zap size={13} className="text-[#D4AF37] shrink-0 mt-0.5" />}
|
{l.featured && <Zap size={13} className="text-[#D4AF37] shrink-0 mt-0.5" />}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-0.5 line-clamp-1">{l.description}</p>
|
<p className="text-xs text-gray-400 mt-0.5 line-clamp-1">{l.description}</p>
|
||||||
<div className="flex items-center justify-between mt-2">
|
<div className="flex items-center justify-between mt-2">
|
||||||
<span className="text-[#D4AF37] font-bold text-sm">{l.price_flh.toLocaleString()} FLH</span>
|
<span className="text-[#D4AF37] font-bold text-sm">{l.price_flh.toLocaleString()} FLH</span>
|
||||||
<span className="text-[11px] text-gray-700">{l.seller}</span>
|
<span className="text-[11px] text-gray-400">{l.seller}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ChevronRight size={16} className="text-gray-700 shrink-0 mt-1" />
|
<ChevronRight size={16} className="text-gray-300 shrink-0 mt-1" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -163,7 +163,7 @@ export default function SouqPage() {
|
|||||||
|
|
||||||
{/* Toast */}
|
{/* Toast */}
|
||||||
{toast && (
|
{toast && (
|
||||||
<div className="fixed bottom-24 left-5 right-5 bg-[#1a1a26] border border-gray-700 rounded-2xl px-4 py-3 text-sm text-white text-center z-50 shadow-xl">
|
<div className="fixed bottom-24 left-5 right-5 bg-gray-900 text-white rounded-2xl px-4 py-3 text-sm text-center z-50 shadow-xl">
|
||||||
{toast}
|
{toast}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -171,46 +171,46 @@ export default function SouqPage() {
|
|||||||
{/* Detail bottom sheet */}
|
{/* Detail bottom sheet */}
|
||||||
{selectedListing && (
|
{selectedListing && (
|
||||||
<div className="fixed inset-0 z-50 flex flex-col justify-end" onClick={() => setSelectedListing(null)}>
|
<div className="fixed inset-0 z-50 flex flex-col justify-end" onClick={() => setSelectedListing(null)}>
|
||||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" />
|
||||||
<div
|
<div
|
||||||
className="relative bg-[#0f0f18] border border-gray-800/60 rounded-t-3xl p-6 pb-10 max-h-[80dvh] overflow-y-auto"
|
className="relative bg-white rounded-t-3xl p-6 pb-10 max-h-[80dvh] overflow-y-auto"
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Handle */}
|
{/* Handle */}
|
||||||
<div className="w-10 h-1 bg-gray-700 rounded-full mx-auto mb-5" />
|
<div className="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-5" />
|
||||||
|
|
||||||
<div className="flex items-start gap-3 mb-4">
|
<div className="flex items-start gap-3 mb-4">
|
||||||
<div className="w-14 h-14 rounded-2xl bg-gray-800/60 flex items-center justify-center text-2xl shrink-0">
|
<div className="w-14 h-14 rounded-2xl bg-gray-100 flex items-center justify-center text-2xl shrink-0">
|
||||||
{CATEGORY_ICONS[selectedListing.category] || '📦'}
|
{CATEGORY_ICONS[selectedListing.category] || '📦'}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h2 className="text-lg font-bold text-white leading-tight">{selectedListing.title}</h2>
|
<h2 className="text-lg font-bold text-gray-900 leading-tight">{selectedListing.title}</h2>
|
||||||
<p className="text-xs text-gray-500 mt-0.5">by {selectedListing.seller}</p>
|
<p className="text-xs text-gray-400 mt-0.5">by {selectedListing.seller}</p>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => setSelectedListing(null)} className="w-8 h-8 flex items-center justify-center rounded-full bg-gray-800">
|
<button onClick={() => setSelectedListing(null)} className="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100">
|
||||||
<X size={15} className="text-gray-400" />
|
<X size={15} className="text-gray-400" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className="inline-block text-[11px] bg-gray-800 text-gray-400 px-3 py-1 rounded-full mb-3">{selectedListing.category}</span>
|
<span className="inline-block text-[11px] bg-gray-100 text-gray-500 px-3 py-1 rounded-full mb-3">{selectedListing.category}</span>
|
||||||
<p className="text-sm text-gray-400 leading-relaxed mb-5">{selectedListing.description}</p>
|
<p className="text-sm text-gray-500 leading-relaxed mb-5">{selectedListing.description}</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-between mb-5">
|
<div className="flex items-center justify-between mb-5">
|
||||||
<p className="text-2xl font-bold text-[#D4AF37]">{selectedListing.price_flh.toLocaleString()} FLH</p>
|
<p className="text-2xl font-bold text-[#D4AF37]">{selectedListing.price_flh.toLocaleString()} FLH</p>
|
||||||
{user && <p className="text-xs text-gray-600">Your balance: {user.flhBalance?.toLocaleString() || 0} FLH</p>}
|
{user && <p className="text-xs text-gray-400">Your balance: {user.flhBalance?.toLocaleString() || 0} FLH</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{token && selectedListing.sellerId !== user?.id ? (
|
{token && selectedListing.sellerId !== user?.id ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => handlePurchase(selectedListing.id)}
|
onClick={() => handlePurchase(selectedListing.id)}
|
||||||
className="w-full flex items-center justify-center gap-2 bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold text-base active:scale-[0.98] transition-transform"
|
className="w-full flex items-center justify-center gap-2 bg-emerald-600 text-white py-4 rounded-2xl font-bold text-base active:scale-[0.97] transition-transform"
|
||||||
>
|
>
|
||||||
<ShoppingCart size={18} /> Purchase Now
|
<ShoppingCart size={18} /> Purchase Now
|
||||||
</button>
|
</button>
|
||||||
) : selectedListing.sellerId === user?.id ? (
|
) : selectedListing.sellerId === user?.id ? (
|
||||||
<div className="w-full text-center py-4 rounded-2xl bg-gray-900 text-gray-500 text-sm">Your listing</div>
|
<div className="w-full text-center py-4 rounded-2xl border border-gray-200 text-gray-400 text-sm">Your listing</div>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={() => setSelectedListing(null)} className="w-full text-center py-4 rounded-2xl bg-gray-900 text-gray-400 text-sm">
|
<button onClick={() => setSelectedListing(null)} className="w-full text-center py-4 rounded-2xl border border-gray-200 text-gray-400 text-sm">
|
||||||
Sign in to purchase
|
Sign in to purchase
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -258,41 +258,41 @@ function CreateListingSheet({ token, onClose, onCreated, showToast }: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex flex-col justify-end" onClick={onClose}>
|
<div className="fixed inset-0 z-50 flex flex-col justify-end" onClick={onClose}>
|
||||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" />
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="relative bg-[#0f0f18] border border-gray-800/60 rounded-t-3xl p-6 pb-10 space-y-4"
|
className="relative bg-white rounded-t-3xl p-6 pb-10 space-y-4"
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="w-10 h-1 bg-gray-700 rounded-full mx-auto mb-2" />
|
<div className="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-2" />
|
||||||
<h2 className="text-lg font-bold text-white">New Listing</h2>
|
<h2 className="text-lg font-bold text-gray-900">New Listing</h2>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text" placeholder="Product title" value={title} onChange={e => setTitle(e.target.value)} required
|
type="text" placeholder="Product title" value={title} onChange={e => setTitle(e.target.value)} required
|
||||||
className="w-full bg-[#0a0a0f] border border-gray-800 rounded-2xl px-4 py-4 text-sm text-white placeholder-gray-600 focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
placeholder="Description" value={description} onChange={e => setDescription(e.target.value)} required rows={3}
|
placeholder="Description" value={description} onChange={e => setDescription(e.target.value)} required rows={3}
|
||||||
className="w-full bg-[#0a0a0f] border border-gray-800 rounded-2xl px-4 py-3 text-sm text-white placeholder-gray-600 focus:border-[#D4AF37]/50 focus:outline-none resize-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none resize-none"
|
||||||
/>
|
/>
|
||||||
<select
|
<select
|
||||||
value={category} onChange={e => setCategory(e.target.value)}
|
value={category} onChange={e => setCategory(e.target.value)}
|
||||||
className="w-full bg-[#0a0a0f] border border-gray-800 rounded-2xl px-4 py-4 text-sm text-white focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-3.5 text-sm text-gray-900 focus:border-emerald-400 focus:outline-none"
|
||||||
>
|
>
|
||||||
{['E-Books', 'Courses', 'Design', 'Audio', 'Video', 'Software', 'Other'].map(c => <option key={c}>{c}</option>)}
|
{['E-Books', 'Courses', 'Design', 'Audio', 'Video', 'Software', 'Other'].map(c => <option key={c}>{c}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<input
|
<input
|
||||||
type="number" placeholder="Price in FLH" value={priceFlh} onChange={e => setPriceFlh(e.target.value)} required min={1}
|
type="number" placeholder="Price in FLH" value={priceFlh} onChange={e => setPriceFlh(e.target.value)} required min={1}
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
className="w-full bg-[#0a0a0f] border border-gray-800 rounded-2xl px-4 py-4 text-sm text-white placeholder-gray-600 focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 text-sm text-gray-900 placeholder-gray-400 focus:border-emerald-400 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit" disabled={loading}
|
type="submit" disabled={loading}
|
||||||
className="w-full bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold disabled:opacity-50 active:scale-[0.98] transition-transform"
|
className="w-full bg-emerald-600 text-white py-4 rounded-2xl font-bold disabled:opacity-50 active:scale-[0.97] transition-transform"
|
||||||
>
|
>
|
||||||
{loading ? 'Creating…' : 'Create Listing'}
|
{loading ? 'Creating…' : 'Create Listing'}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" onClick={onClose} className="w-full text-gray-600 text-sm py-2">
|
<button type="button" onClick={onClose} className="w-full text-gray-400 text-sm py-2">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+35
-32
@@ -21,7 +21,7 @@ const TIERS = [
|
|||||||
cta: 'Continue Free',
|
cta: 'Continue Free',
|
||||||
highlighted: false,
|
highlighted: false,
|
||||||
icon: Zap,
|
icon: Zap,
|
||||||
iconColor: 'text-gray-500',
|
iconColor: 'text-gray-400',
|
||||||
badge: null,
|
badge: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -55,7 +55,7 @@ const TIERS = [
|
|||||||
cta: 'Go Pro',
|
cta: 'Go Pro',
|
||||||
highlighted: false,
|
highlighted: false,
|
||||||
icon: Crown,
|
icon: Crown,
|
||||||
iconColor: 'text-purple-400',
|
iconColor: 'text-purple-500',
|
||||||
badge: null,
|
badge: null,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -101,60 +101,63 @@ function UpgradeContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authLoading) return (
|
if (authLoading) return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] flex items-center justify-center pb-20">
|
<div className="min-h-dvh bg-[#f5f4f0] flex items-center justify-center pb-20">
|
||||||
<div className="w-8 h-8 rounded-full border-2 border-[#D4AF37]/30 border-t-[#D4AF37] animate-spin" />
|
<div className="w-8 h-8 rounded-full border-2 border-emerald-300 border-t-emerald-600 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
if (!token) return null
|
if (!token) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] pb-24">
|
<div className="min-h-dvh bg-[#f5f4f0] pb-24">
|
||||||
|
|
||||||
{/* Header */}
|
{/* Gradient header */}
|
||||||
<div className="flex items-center gap-3 px-4 pt-6 pb-4">
|
<div className="bg-gradient-to-br from-emerald-700 to-teal-600 px-4 pt-12 pb-8">
|
||||||
<button onClick={() => router.back()} className="w-9 h-9 flex items-center justify-center rounded-xl bg-gray-900 text-gray-400">
|
<button
|
||||||
|
onClick={() => router.back()}
|
||||||
|
className="w-9 h-9 flex items-center justify-center rounded-xl bg-white/20 text-white mb-4 active:scale-[0.97] transition-all"
|
||||||
|
>
|
||||||
<ChevronLeft size={20} />
|
<ChevronLeft size={20} />
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<h1 className="text-2xl font-bold text-white">Choose your plan</h1>
|
||||||
<h1 className="text-lg font-bold text-white">Choose your plan</h1>
|
<p className="text-sm text-white/70 mt-1">Cancel anytime, no hidden fees</p>
|
||||||
<p className="text-xs text-gray-500">Cancel anytime</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Trial reminder */}
|
{/* Trial reminder */}
|
||||||
<div className="mx-4 mb-5 bg-[#D4AF37]/8 border border-[#D4AF37]/20 rounded-2xl px-4 py-3">
|
<div className="mx-4 -mt-4 mb-5 bg-white border border-emerald-100 rounded-2xl shadow-sm px-4 py-3">
|
||||||
<p className="text-xs text-[#D4AF37] font-medium">✨ New accounts include a 7-day Premium trial — free</p>
|
<p className="text-xs text-emerald-700 font-medium">New accounts include a 7-day Premium trial — free</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tiers */}
|
{/* Tiers */}
|
||||||
<div className="px-4 space-y-3">
|
<div className="px-4 space-y-4">
|
||||||
{TIERS.map((tier) => {
|
{TIERS.map((tier) => {
|
||||||
const Icon = tier.icon
|
const Icon = tier.icon
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={tier.name}
|
key={tier.name}
|
||||||
className={`relative rounded-3xl border p-5 ${
|
className={`relative bg-white rounded-3xl shadow-md border p-5 ${
|
||||||
tier.highlighted
|
tier.highlighted
|
||||||
? 'bg-gradient-to-b from-[#D4AF37]/8 to-[#111118] border-[#D4AF37]/40'
|
? 'border-emerald-400'
|
||||||
: 'bg-[#111118] border-gray-800/60'
|
: 'border-gray-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{tier.badge && (
|
{tier.badge && (
|
||||||
<div className="absolute -top-3 left-5 bg-[#D4AF37] text-[#0a0a0f] text-[10px] font-bold px-3 py-1 rounded-full tracking-widest">
|
<div className="absolute -top-3 left-5 bg-gradient-to-r from-[#D4AF37] to-amber-500 text-white text-[10px] font-bold px-3 py-1 rounded-full tracking-widest">
|
||||||
{tier.badge}
|
{tier.badge}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`w-10 h-10 rounded-2xl flex items-center justify-center ${tier.highlighted ? 'bg-[#D4AF37]/15' : 'bg-gray-800/60'}`}>
|
<div className={`w-10 h-10 rounded-2xl flex items-center justify-center ${
|
||||||
|
tier.highlighted ? 'bg-amber-50' : 'bg-gray-50'
|
||||||
|
}`}>
|
||||||
<Icon size={20} className={tier.iconColor} />
|
<Icon size={20} className={tier.iconColor} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="font-bold text-white">{tier.name}</h2>
|
<h2 className="font-bold text-gray-900">{tier.name}</h2>
|
||||||
<div className="flex items-baseline gap-0.5">
|
<div className="flex items-baseline gap-0.5">
|
||||||
<span className="text-xl font-bold text-white">{tier.price}</span>
|
<span className="text-xl font-bold text-gray-900">{tier.price}</span>
|
||||||
<span className="text-gray-600 text-xs">{tier.period}</span>
|
<span className="text-gray-400 text-xs">{tier.period}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,8 +166,8 @@ function UpgradeContent() {
|
|||||||
<ul className="space-y-2.5 mb-5">
|
<ul className="space-y-2.5 mb-5">
|
||||||
{tier.features.map((f) => (
|
{tier.features.map((f) => (
|
||||||
<li key={f} className="flex items-start gap-2.5">
|
<li key={f} className="flex items-start gap-2.5">
|
||||||
<Check size={14} className="text-[#D4AF37] mt-0.5 shrink-0" />
|
<Check size={14} className="text-emerald-500 mt-0.5 shrink-0" />
|
||||||
<span className="text-sm text-gray-400">{f}</span>
|
<span className="text-sm text-gray-600">{f}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -172,10 +175,10 @@ function UpgradeContent() {
|
|||||||
{tier.priceId ? (
|
{tier.priceId ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleSubscribe(tier.priceId!)}
|
onClick={() => handleSubscribe(tier.priceId!)}
|
||||||
className={`w-full py-4 rounded-2xl font-bold text-sm active:scale-[0.98] transition-all ${
|
className={`w-full py-4 rounded-2xl font-bold text-sm active:scale-[0.97] transition-all ${
|
||||||
tier.highlighted
|
tier.highlighted
|
||||||
? 'bg-[#D4AF37] text-[#0a0a0f]'
|
? 'bg-emerald-600 text-white'
|
||||||
: 'bg-purple-500/20 text-purple-300 border border-purple-500/30'
|
: 'bg-purple-50 text-purple-600 border border-purple-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{tier.cta}
|
{tier.cta}
|
||||||
@@ -183,7 +186,7 @@ function UpgradeContent() {
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push(tier.href ?? '/')}
|
onClick={() => router.push(tier.href ?? '/')}
|
||||||
className="w-full py-4 rounded-2xl font-bold text-sm border border-gray-700 text-gray-400 active:bg-gray-800 transition"
|
className="w-full py-4 rounded-2xl font-bold text-sm border border-gray-200 text-gray-500 bg-gray-50 active:scale-[0.97] transition-all"
|
||||||
>
|
>
|
||||||
{tier.cta}
|
{tier.cta}
|
||||||
</button>
|
</button>
|
||||||
@@ -193,7 +196,7 @@ function UpgradeContent() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-center text-xs text-gray-700 mt-6 px-8">
|
<p className="text-center text-xs text-gray-400 mt-6 px-8">
|
||||||
Secure payments via Polar. Cancel anytime from your account settings.
|
Secure payments via Polar. Cancel anytime from your account settings.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,8 +206,8 @@ function UpgradeContent() {
|
|||||||
export default function UpgradePage() {
|
export default function UpgradePage() {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={
|
<Suspense fallback={
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] flex items-center justify-center pb-20">
|
<div className="min-h-dvh bg-[#f5f4f0] flex items-center justify-center pb-20">
|
||||||
<div className="w-8 h-8 rounded-full border-2 border-[#D4AF37]/30 border-t-[#D4AF37] animate-spin" />
|
<div className="w-8 h-8 rounded-full border-2 border-emerald-300 border-t-emerald-600 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
}>
|
}>
|
||||||
<UpgradeContent />
|
<UpgradeContent />
|
||||||
|
|||||||
+33
-33
@@ -37,8 +37,8 @@ export default function WalletPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authLoading) return (
|
if (authLoading) return (
|
||||||
<div className="min-h-dvh flex items-center justify-center bg-[#0a0a0f] pb-20">
|
<div className="min-h-dvh flex items-center justify-center bg-[#f5f4f0] pb-20">
|
||||||
<div className="w-8 h-8 rounded-full border-2 border-[#D4AF37]/30 border-t-[#D4AF37] animate-spin" />
|
<div className="w-8 h-8 rounded-full border-2 border-emerald-200 border-t-emerald-600 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
if (!token) return null
|
if (!token) return null
|
||||||
@@ -46,38 +46,38 @@ export default function WalletPage() {
|
|||||||
const fiatValue = amount ? ((parseInt(amount) || 0) * 0.008).toFixed(2) : '0.00'
|
const fiatValue = amount ? ((parseInt(amount) || 0) * 0.008).toFixed(2) : '0.00'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-[#0a0a0f] pb-24">
|
<div className="min-h-dvh bg-[#f5f4f0] pb-24">
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="px-5 pt-6 pb-5">
|
<div className="px-5 pt-8 pb-4 bg-white">
|
||||||
<h1 className="text-xl font-bold text-white">Wallet</h1>
|
<h1 className="text-xl font-bold text-gray-900">Wallet</h1>
|
||||||
<p className="text-[11px] text-gray-600 mt-0.5">Manage your FLH balance</p>
|
<p className="text-[11px] text-gray-400 mt-0.5">Manage your FLH balance</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Balance card */}
|
{/* Balance card */}
|
||||||
<div className="mx-5 mb-5 rounded-3xl overflow-hidden" style={{ background: 'linear-gradient(135deg, #1a1508 0%, #0f0f18 100%)', border: '1px solid rgba(212,175,55,0.2)' }}>
|
<div className="mx-5 mt-5 mb-5 rounded-2xl overflow-hidden shadow-sm bg-gradient-to-br from-emerald-600 to-teal-600">
|
||||||
<div className="px-6 pt-6 pb-5">
|
<div className="px-6 pt-6 pb-6">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Wallet size={16} className="text-[#D4AF37]" />
|
<Wallet size={16} className="text-white/70" />
|
||||||
<span className="text-xs text-[#D4AF37]/70 font-medium uppercase tracking-wider">FLH Balance</span>
|
<span className="text-xs text-white/70 font-medium uppercase tracking-wider">FLH Balance</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-5xl font-bold text-white tabular-nums leading-none">
|
<p className="text-5xl font-bold text-white tabular-nums leading-none">
|
||||||
{(user?.flhBalance || 0).toLocaleString()}
|
{(user?.flhBalance || 0).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[#D4AF37] font-semibold mt-1 text-base">FLH Tokens</p>
|
<p className="text-white/80 font-semibold mt-1 text-base">FLH Tokens</p>
|
||||||
<p className="text-xs text-gray-600 mt-3">
|
<p className="text-xs text-white/50 mt-3">
|
||||||
≈ £{((user?.flhBalance || 0) * 0.008).toFixed(2)} GBP at current rate
|
≈ £{((user?.flhBalance || 0) * 0.008).toFixed(2)} GBP at current rate
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cash Out */}
|
{/* Cash Out */}
|
||||||
<div className="mx-5 mb-5 bg-[#111118] border border-gray-800/60 rounded-3xl p-5">
|
<div className="mx-5 mb-5 bg-white border border-gray-100 rounded-2xl shadow-sm p-5">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<ArrowDownLeft size={16} className="text-[#D4AF37]" />
|
<ArrowDownLeft size={16} className="text-emerald-600" />
|
||||||
<h2 className="font-bold text-white text-sm">Cash Out</h2>
|
<h2 className="font-bold text-gray-900 text-sm">Cash Out</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[11px] text-gray-600 mb-4">100 FLH = £0.80 · Minimum 100 FLH</p>
|
<p className="text-[11px] text-gray-400 mb-4">100 FLH = £0.80 · Minimum 100 FLH</p>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<input
|
<input
|
||||||
@@ -87,18 +87,18 @@ export default function WalletPage() {
|
|||||||
onChange={e => setAmount(e.target.value)}
|
onChange={e => setAmount(e.target.value)}
|
||||||
min={100}
|
min={100}
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
className="w-full bg-[#0d0d14] border border-gray-800 rounded-2xl px-4 py-4 text-white placeholder-gray-700 text-sm focus:border-[#D4AF37]/50 focus:outline-none"
|
className="w-full bg-gray-50 border border-gray-200 rounded-xl px-4 py-4 text-gray-900 placeholder-gray-400 text-sm focus:border-emerald-400 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
{amount && parseInt(amount) > 0 && (
|
{amount && parseInt(amount) > 0 && (
|
||||||
<div className="flex items-center justify-between px-1">
|
<div className="flex items-center justify-between px-1">
|
||||||
<span className="text-xs text-gray-600">{parseInt(amount).toLocaleString()} FLH</span>
|
<span className="text-xs text-gray-400">{parseInt(amount).toLocaleString()} FLH</span>
|
||||||
<span className="text-xs text-[#D4AF37] font-semibold">£{fiatValue} GBP</span>
|
<span className="text-xs text-emerald-600 font-semibold">£{fiatValue} GBP</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={handleCashout}
|
onClick={handleCashout}
|
||||||
disabled={loading || !amount || parseInt(amount) < 100}
|
disabled={loading || !amount || parseInt(amount) < 100}
|
||||||
className="w-full bg-[#D4AF37] text-[#0a0a0f] py-4 rounded-2xl font-bold text-sm disabled:opacity-40 active:scale-[0.98] transition-all"
|
className="w-full bg-emerald-600 text-white py-4 rounded-2xl font-bold text-sm disabled:opacity-40 active:scale-[0.97] transition-all"
|
||||||
>
|
>
|
||||||
{loading ? 'Submitting…' : 'Request Cash Out'}
|
{loading ? 'Submitting…' : 'Request Cash Out'}
|
||||||
</button>
|
</button>
|
||||||
@@ -109,32 +109,32 @@ export default function WalletPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* History */}
|
{/* History */}
|
||||||
<div className="mx-5 bg-[#111118] border border-gray-800/60 rounded-3xl p-5">
|
<div className="mx-5 bg-white border border-gray-100 rounded-2xl shadow-sm p-5">
|
||||||
<h2 className="font-bold text-white text-sm mb-4">History</h2>
|
<h2 className="font-bold text-gray-900 text-sm mb-4">History</h2>
|
||||||
{cashouts.length === 0 ? (
|
{cashouts.length === 0 ? (
|
||||||
<div className="text-center py-6">
|
<div className="text-center py-6">
|
||||||
<p className="text-gray-600 text-sm">No cashout requests yet</p>
|
<p className="text-gray-400 text-sm">No cashout requests yet</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{cashouts.map(c => (
|
{cashouts.map(c => (
|
||||||
<div key={c.id} className="flex items-center gap-3 py-2">
|
<div key={c.id} className="flex items-center gap-3 py-2">
|
||||||
<div className="w-9 h-9 rounded-full bg-gray-800 flex items-center justify-center shrink-0">
|
<div className="w-9 h-9 rounded-full bg-gray-100 flex items-center justify-center shrink-0">
|
||||||
{c.status === 'pending'
|
{c.status === 'pending'
|
||||||
? <Clock size={16} className="text-yellow-400" />
|
? <Clock size={16} className="text-yellow-500" />
|
||||||
: c.status === 'approved'
|
: c.status === 'approved'
|
||||||
? <CheckCircle2 size={16} className="text-emerald-400" />
|
? <CheckCircle2 size={16} className="text-emerald-600" />
|
||||||
: <XCircle size={16} className="text-red-400" />
|
: <XCircle size={16} className="text-red-500" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm text-white font-medium">{c.amountFlh.toLocaleString()} FLH</p>
|
<p className="text-sm text-gray-900 font-medium">{c.amountFlh.toLocaleString()} FLH</p>
|
||||||
<p className="text-xs text-gray-600">£{c.fiatAmount.toFixed(2)} GBP</p>
|
<p className="text-xs text-gray-400">£{c.fiatAmount.toFixed(2)} GBP</p>
|
||||||
</div>
|
</div>
|
||||||
<span className={`text-xs font-semibold capitalize px-2.5 py-1 rounded-full ${
|
<span className={`text-xs font-semibold capitalize px-2.5 py-1 rounded-full ${
|
||||||
c.status === 'pending' ? 'bg-yellow-400/10 text-yellow-400'
|
c.status === 'pending' ? 'bg-yellow-50 text-yellow-700'
|
||||||
: c.status === 'approved' ? 'bg-emerald-400/10 text-emerald-400'
|
: c.status === 'approved' ? 'bg-emerald-50 text-emerald-700'
|
||||||
: 'bg-red-400/10 text-red-400'
|
: 'bg-red-50 text-red-600'
|
||||||
}`}>{c.status}</span>
|
}`}>{c.status}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -144,7 +144,7 @@ export default function WalletPage() {
|
|||||||
|
|
||||||
{/* Toast */}
|
{/* Toast */}
|
||||||
{toast && (
|
{toast && (
|
||||||
<div className="fixed bottom-24 left-5 right-5 bg-[#1a1a26] border border-gray-700 rounded-2xl px-4 py-3 text-sm text-white text-center z-50 shadow-xl">
|
<div className="fixed bottom-24 left-5 right-5 bg-gray-900 text-white rounded-2xl px-4 py-3 text-sm text-center z-50 shadow-xl">
|
||||||
{toast}
|
{toast}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,27 +2,47 @@
|
|||||||
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import { Bot, ShoppingBag, Moon, User, Repeat2 } from 'lucide-react'
|
import { Home, Moon, Bot, Repeat2, User } from 'lucide-react'
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ href: '/nur', icon: Bot, label: 'Nur' },
|
{ href: '/home', icon: Home, label: 'Home' },
|
||||||
{ href: '/prayer', icon: Moon, label: 'Prayer' },
|
{ href: '/prayer', icon: Moon, label: 'Prayer' },
|
||||||
{ href: '/dhikr', icon: Repeat2, label: 'Dhikr' },
|
{ href: '/nur', icon: Bot, label: 'Nur', center: true },
|
||||||
{ href: '/souq', icon: ShoppingBag, label: 'Souq' },
|
{ href: '/dhikr', icon: Repeat2, label: 'Dhikr' },
|
||||||
{ href: '/profile', icon: User, label: 'Profile' },
|
{ href: '/profile',icon: User, label: 'Profile' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function BottomNav() {
|
export default function BottomNav() {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
if (pathname === '/login' || pathname === '/register') return null
|
if (pathname === '/login' || pathname === '/register') return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-[#0a0a0f]/95 backdrop-blur-md border-t border-gray-800/60"
|
<nav
|
||||||
style={{ paddingBottom: 'env(safe-area-inset-bottom, 0px)', height: 'calc(64px + env(safe-area-inset-bottom, 0px))' }}>
|
className="fixed bottom-0 left-0 right-0 z-50 bg-white border-t border-gray-100"
|
||||||
<div className="flex items-stretch h-16">
|
style={{ paddingBottom: 'env(safe-area-inset-bottom, 0px)', height: 'calc(64px + env(safe-area-inset-bottom, 0px))' }}
|
||||||
{tabs.map(({ href, icon: Icon, label }) => {
|
>
|
||||||
const isActive = pathname === href || pathname.startsWith(href + '/')
|
<div className="flex items-stretch h-16 px-2">
|
||||||
|
{tabs.map(({ href, icon: Icon, label, center }) => {
|
||||||
|
const isActive = pathname === href || (href !== '/home' && pathname.startsWith(href + '/'))
|
||||||
|
|
||||||
|
if (center) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={href}
|
||||||
|
href={href}
|
||||||
|
className="flex-1 flex flex-col items-center justify-center gap-0.5"
|
||||||
|
>
|
||||||
|
<div className={`w-12 h-12 rounded-2xl flex items-center justify-center shadow-sm transition-all active:scale-95 ${
|
||||||
|
isActive
|
||||||
|
? 'bg-gradient-to-br from-violet-500 to-purple-600'
|
||||||
|
: 'bg-gradient-to-br from-emerald-500 to-teal-600'
|
||||||
|
}`}>
|
||||||
|
<Icon size={22} className="text-white" strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={href}
|
key={href}
|
||||||
@@ -31,10 +51,10 @@ export default function BottomNav() {
|
|||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
size={22}
|
size={22}
|
||||||
className={isActive ? 'text-[#D4AF37]' : 'text-gray-600'}
|
className={isActive ? 'text-emerald-600' : 'text-gray-400'}
|
||||||
strokeWidth={isActive ? 2.5 : 1.8}
|
strokeWidth={isActive ? 2.5 : 1.8}
|
||||||
/>
|
/>
|
||||||
<span className={`text-[10px] font-medium ${isActive ? 'text-[#D4AF37]' : 'text-gray-600'}`}>
|
<span className={`text-[10px] font-medium ${isActive ? 'text-emerald-600' : 'text-gray-400'}`}>
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user