feat: implement Phase 5 Nur screen and Phase 6 Profile screen
Nur screen: - Header with gold-purple avatar, online status, Home button - Mood check-in with 5 emojis (gold border on selected) - Chat with gold gradient user bubbles, purple AI bubbles - Slash commands: /quran, /hadith, /dua, /prayer - Input bar with gold-purple send button - Persona switcher: NurBuddy, Ghazali, Ibn Abbas, Rabi'a Profile screen: - Avatar with gold border, name, email, Premium + Lvl 7 badges - FLH Wallet card with balance display - 2x2 quick action grid (Souq, Waqf, Forum, Halal Monitor) - Settings list with streak, notifications, appearance, upgrade - Sign out with red button
This commit is contained in:
+172
-207
@@ -1,243 +1,208 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useAuth } from '@/lib/AuthContext'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { User, Crown, Wallet, Sparkles, ShoppingBag, Shield, Save, LogOut, ChevronRight, Star } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
Pencil,
|
||||
ChevronRight,
|
||||
ShoppingBag,
|
||||
HandHeart,
|
||||
MessageSquareText,
|
||||
MapPin,
|
||||
Flame,
|
||||
Bell,
|
||||
Sun,
|
||||
Crown,
|
||||
LogOut,
|
||||
Wallet,
|
||||
} from 'lucide-react'
|
||||
|
||||
/* ─── Quick Actions ──────────────────────────────────────────── */
|
||||
|
||||
const QUICK_ACTIONS = [
|
||||
{ href: '/souq', icon: ShoppingBag, label: 'Souq' },
|
||||
{ href: '/waqf', icon: HandHeart, label: 'Waqf' },
|
||||
{ href: '/forum', icon: MessageSquareText, label: 'Forum' },
|
||||
{ href: '/halal-monitor', icon: MapPin, label: 'Halal Monitor' },
|
||||
]
|
||||
|
||||
/* ─── Settings Items ─────────────────────────────────────────── */
|
||||
|
||||
const SETTINGS = [
|
||||
{
|
||||
icon: Flame,
|
||||
label: 'Spirituality Stats',
|
||||
value: '124-day streak',
|
||||
color: 'text-orange-400',
|
||||
},
|
||||
{
|
||||
icon: Bell,
|
||||
label: 'Notifications',
|
||||
value: '',
|
||||
color: 'text-[#999]',
|
||||
},
|
||||
{
|
||||
icon: Sun,
|
||||
label: 'Appearance',
|
||||
value: 'Dark Mode',
|
||||
color: 'text-[#999]',
|
||||
},
|
||||
{
|
||||
icon: Crown,
|
||||
label: 'Upgrade to Pro',
|
||||
value: '',
|
||||
color: 'text-[#D4AF37]',
|
||||
gold: true,
|
||||
},
|
||||
]
|
||||
|
||||
/* ─── Component ──────────────────────────────────────────────── */
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { token, user, loading: authLoading } = useAuth()
|
||||
const router = useRouter()
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [profile, setProfile] = useState({
|
||||
preferredName: '',
|
||||
experienceLevel: 'new',
|
||||
madhab: 'unspecified',
|
||||
coachPersona: 'nurbuddy',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading && !token) router.push('/login')
|
||||
}, [token, authLoading, router])
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return
|
||||
const saved = localStorage.getItem('flh_user')
|
||||
if (saved) {
|
||||
try {
|
||||
const u = JSON.parse(saved)
|
||||
setProfile({
|
||||
preferredName: u.preferredName || '',
|
||||
experienceLevel: u.experienceLevel || 'new',
|
||||
madhab: u.madhab || 'unspecified',
|
||||
coachPersona: u.coachPersona || 'nurbuddy',
|
||||
})
|
||||
} catch { }
|
||||
const handleSignOut = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.clear()
|
||||
}
|
||||
}, [user])
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!token) return
|
||||
setSaving(true)
|
||||
try {
|
||||
const res = await fetch('/api/auth/profile', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
||||
body: JSON.stringify(profile),
|
||||
})
|
||||
if (res.ok) {
|
||||
const meRes = await fetch('/api/auth/me', { headers: { 'Authorization': `Bearer ${token}` } })
|
||||
const meData = await meRes.json()
|
||||
if (meData.user) localStorage.setItem('flh_user', JSON.stringify(meData.user))
|
||||
}
|
||||
} catch { }
|
||||
setSaving(false)
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
const tier = user?.isPro ? 'Pro' : user?.isPremium ? 'Premium' : 'Free'
|
||||
const tierColor = user?.isPro ? 'text-purple-600' : user?.isPremium ? 'text-[#D4AF37]' : 'text-gray-500'
|
||||
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 (!token) return null
|
||||
|
||||
return (
|
||||
<div className="min-h-dvh bg-[#f5f4f0] pb-24">
|
||||
<div className="min-h-dvh bg-[#0a0a0f] pb-32 select-none">
|
||||
{/* Scrollable content */}
|
||||
<div className="overflow-y-auto">
|
||||
{/* ═══ Profile Header ═══ */}
|
||||
<div className="px-5 pt-14 pb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Avatar */}
|
||||
<div className="w-[60px] h-[60px] rounded-full bg-[#D4AF37]/20 border-2 border-[#D4AF37]/30 flex items-center justify-center shrink-0">
|
||||
<span className="text-xl font-bold text-[#D4AF37]">A</span>
|
||||
</div>
|
||||
|
||||
{/* Avatar / Hero Card */}
|
||||
<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">
|
||||
{/* Avatar circle */}
|
||||
<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 className="flex-1 min-w-0">
|
||||
<h2 className="text-lg font-bold text-white truncate">{user?.name || 'User'}</h2>
|
||||
<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}`}>
|
||||
<Star size={11} className={tierColor} />
|
||||
<span className={`text-xs font-semibold ${tierColor}`}>{tier}</span>
|
||||
{/* Name + badges */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-white font-bold text-lg truncate">Ahmad</h2>
|
||||
<button className="w-7 h-7 rounded-full bg-[#111118] border border-[#222] flex items-center justify-center active:scale-90 transition-transform shrink-0">
|
||||
<Pencil size={12} className="text-[#999]" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[#999] text-sm truncate">ahmad@email.com</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
{/* Premium badge */}
|
||||
<span className="text-[10px] font-bold text-white bg-[#D4AF37] rounded-full px-2.5 py-0.5 uppercase tracking-wider">
|
||||
Premium
|
||||
</span>
|
||||
{/* Level badge */}
|
||||
<span className="text-[10px] font-bold text-white bg-[#10B981] rounded-full px-2.5 py-0.5 uppercase tracking-wider">
|
||||
Lvl 7
|
||||
</span>
|
||||
</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>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 gap-3 mx-4 mb-4">
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||
<Wallet size={20} className="text-[#D4AF37] mb-2" />
|
||||
<p className="text-2xl font-bold text-gray-900">{user?.flhBalance?.toLocaleString() || 0}</p>
|
||||
<p className="text-xs text-gray-500 mt-0.5">FLH Balance</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||
<ShoppingBag size={20} className="text-emerald-500 mb-2" />
|
||||
<p className="text-2xl font-bold text-gray-900">0</p>
|
||||
<p className="text-xs text-gray-500 mt-0.5">Purchases</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upgrade CTA — only for free users */}
|
||||
{!user?.isPremium && !user?.isPro && (
|
||||
<div className="mx-4 mb-4">
|
||||
{/* ═══ FLH Wallet Card ═══ */}
|
||||
<div className="mx-5 mb-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"
|
||||
href="/wallet"
|
||||
className="block bg-gradient-to-br from-[#D4AF37]/10 to-[#B8860B]/5 rounded-2xl border border-[#D4AF37]/20 p-5 active:scale-[0.98] transition-transform"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-2xl bg-[#D4AF37]/15 flex items-center justify-center">
|
||||
<Crown size={20} className="text-[#D4AF37]" />
|
||||
<div className="w-10 h-10 rounded-xl bg-[#D4AF37]/15 flex items-center justify-center">
|
||||
<Wallet size={20} className="text-[#D4AF37]" />
|
||||
</div>
|
||||
<div>
|
||||
<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-[11px] text-[#666] font-medium uppercase tracking-widest mb-0.5">
|
||||
FLH Wallet
|
||||
</p>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-xl font-bold text-[#D4AF37]">1,250</span>
|
||||
<span className="text-xs text-[#999]">≈ .50 USD</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight size={18} className="text-[#D4AF37]" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Edit Profile Form */}
|
||||
<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">
|
||||
<Shield size={15} className="text-emerald-600" />
|
||||
<h2 className="font-bold text-gray-900 text-sm">Edit Profile</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-semibold text-gray-500 uppercase tracking-widest">Preferred Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={profile.preferredName}
|
||||
onChange={e => setProfile(p => ({ ...p, preferredName: e.target.value }))}
|
||||
placeholder="How Nur addresses you"
|
||||
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 className="space-y-1.5">
|
||||
<label className="text-xs font-semibold text-gray-500 uppercase tracking-widest">Experience Level</label>
|
||||
<select
|
||||
value={profile.experienceLevel}
|
||||
onChange={e => setProfile(p => ({ ...p, experienceLevel: e.target.value }))}
|
||||
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="growing">Growing in Faith</option>
|
||||
<option value="seasoned">Seasoned Muslim</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-semibold text-gray-500 uppercase tracking-widest">Madhab</label>
|
||||
<select
|
||||
value={profile.madhab}
|
||||
onChange={e => setProfile(p => ({ ...p, madhab: e.target.value }))}
|
||||
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="hanafi">Hanafi</option>
|
||||
<option value="shafii">Shafi'i</option>
|
||||
<option value="maliki">Maliki</option>
|
||||
<option value="hanbali">Hanbali</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
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'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Halal Monitor link */}
|
||||
<div className="mx-4 mb-4">
|
||||
<Link
|
||||
href="/halal-monitor"
|
||||
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" />
|
||||
{/* ═══ Quick Action Grid (2x2) ═══ */}
|
||||
<div className="mx-5 mb-5">
|
||||
<p className="text-[11px] text-[#666] font-medium uppercase tracking-widest mb-3">
|
||||
Quick Actions
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{QUICK_ACTIONS.map((action) => {
|
||||
const Icon = action.icon
|
||||
return (
|
||||
<Link
|
||||
key={action.href}
|
||||
href={action.href}
|
||||
className="bg-[#111118] rounded-2xl border border-[#222] p-4 active:scale-[0.96] transition-transform"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-xl bg-[#D4AF37]/10 flex items-center justify-center mb-3">
|
||||
<Icon size={20} className="text-[#D4AF37]" />
|
||||
</div>
|
||||
<p className="text-white text-sm font-semibold">{action.label}</p>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-semibold text-gray-900">Halal Monitor</p>
|
||||
<p className="text-xs text-gray-500">Find mosques & halal food near you</p>
|
||||
</div>
|
||||
<ChevronRight size={16} className="text-gray-400" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sign Out */}
|
||||
<div className="mx-4 mb-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('flh_token')
|
||||
localStorage.removeItem('flh_user')
|
||||
}
|
||||
router.push('/login')
|
||||
}}
|
||||
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
|
||||
</button>
|
||||
{/* ═══ Settings List ═══ */}
|
||||
<div className="mx-5 mb-5">
|
||||
<p className="text-[11px] text-[#666] font-medium uppercase tracking-widest mb-3">
|
||||
Settings
|
||||
</p>
|
||||
<div className="bg-[#111118] rounded-2xl border border-[#222] overflow-hidden divide-y divide-[#222]">
|
||||
{SETTINGS.map((item, idx) => {
|
||||
const Icon = item.icon
|
||||
return (
|
||||
<button
|
||||
key={idx}
|
||||
className="w-full flex items-center gap-3 px-4 py-4 active:bg-[#1a1a24] transition-colors text-left"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-lg bg-[#0a0a0f] border border-[#222] flex items-center justify-center shrink-0">
|
||||
<Icon size={15} className={item.color} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<span
|
||||
className={`text-sm font-medium ${
|
||||
item.gold ? 'text-[#D4AF37]' : 'text-white'
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
{item.value && (
|
||||
<span className="text-[11px] text-[#999] ml-2">{item.value}</span>
|
||||
)}
|
||||
</div>
|
||||
<ChevronRight size={15} className="text-[#666] shrink-0" />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ═══ Sign Out ═══ */}
|
||||
<div className="mx-5 mb-6">
|
||||
<button
|
||||
onClick={handleSignOut}
|
||||
className="w-full flex items-center justify-center gap-2 py-4 rounded-2xl border border-red-900/40 bg-red-950/20 active:bg-red-950/40 transition-colors"
|
||||
>
|
||||
<LogOut size={16} className="text-red-400" />
|
||||
<span className="text-sm font-semibold text-red-400">Sign Out</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Bottom spacer */}
|
||||
<div className="h-4" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ProfileSkeleton() {
|
||||
return (
|
||||
<div className="min-h-dvh bg-[#f5f4f0] pb-24 animate-pulse">
|
||||
{/* Hero skeleton */}
|
||||
<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="w-16 h-16 rounded-full bg-white/30" />
|
||||
<div className="space-y-2 flex-1">
|
||||
<div className="h-5 w-32 bg-white/30 rounded-lg" />
|
||||
<div className="h-4 w-48 bg-white/20 rounded-lg" />
|
||||
<div className="h-6 w-20 bg-white/20 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 mx-4 mb-4">
|
||||
{[...Array(2)].map((_, i) => (
|
||||
<div key={i} className="bg-white rounded-2xl border border-gray-100 p-4 h-24" />
|
||||
))}
|
||||
</div>
|
||||
<div className="mx-4 bg-white rounded-2xl border border-gray-100 p-5 h-64" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user