233 lines
9.7 KiB
TypeScript
233 lines
9.7 KiB
TypeScript
|
|
'use client'
|
||
|
|
|
||
|
|
import { useState, useEffect } from 'react'
|
||
|
|
import { useAuth } from '@/lib/AuthContext'
|
||
|
|
import { useRouter } from 'next/navigation'
|
||
|
|
import { User, Crown, Wallet, Sparkles, ShoppingBag, Shield, Save, LogOut, ChevronRight, Star } from 'lucide-react'
|
||
|
|
import Link from 'next/link'
|
||
|
|
|
||
|
|
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 { }
|
||
|
|
}
|
||
|
|
}, [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)
|
||
|
|
}
|
||
|
|
|
||
|
|
const tier = user?.isPro ? 'Pro' : user?.isPremium ? 'Premium' : 'Free'
|
||
|
|
const tierColor = user?.isPro ? 'text-purple-400' : 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'
|
||
|
|
|
||
|
|
if (authLoading) return <ProfileSkeleton />
|
||
|
|
if (!token) return null
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-dvh bg-[#0a0a0f] pb-24">
|
||
|
|
|
||
|
|
{/* Header */}
|
||
|
|
<div className="px-5 pt-6 pb-5">
|
||
|
|
<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="w-16 h-16 rounded-2xl bg-[#D4AF37]/10 border border-[#D4AF37]/20 flex items-center justify-center shrink-0">
|
||
|
|
<User size={28} className="text-[#D4AF37]" />
|
||
|
|
</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-gray-500 truncate">{user?.email || ''}</p>
|
||
|
|
<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>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Stats */}
|
||
|
|
<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">
|
||
|
|
<Wallet size={20} className="text-[#D4AF37] mb-2" />
|
||
|
|
<p className="text-2xl font-bold text-white">{user?.flhBalance?.toLocaleString() || 0}</p>
|
||
|
|
<p className="text-xs text-gray-600 mt-0.5">FLH Balance</p>
|
||
|
|
</div>
|
||
|
|
<div className="bg-[#111118] border border-gray-800/60 rounded-2xl p-4">
|
||
|
|
<ShoppingBag size={20} className="text-blue-400 mb-2" />
|
||
|
|
<p className="text-2xl font-bold text-white">0</p>
|
||
|
|
<p className="text-xs text-gray-600 mt-0.5">Purchases</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Upgrade CTA — only for free users */}
|
||
|
|
{!user?.isPremium && !user?.isPro && (
|
||
|
|
<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">
|
||
|
|
<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>
|
||
|
|
<div>
|
||
|
|
<p className="font-bold text-[#D4AF37] text-sm">Go Premium</p>
|
||
|
|
<p className="text-xs text-gray-500">Unlock scholars & unlimited chat</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<ChevronRight size={18} className="text-[#D4AF37]" />
|
||
|
|
</div>
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 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="flex items-center gap-2 mb-1">
|
||
|
|
<Shield size={15} className="text-[#D4AF37]" />
|
||
|
|
<h2 className="font-bold text-white text-sm">Edit Profile</h2>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="space-y-1.5">
|
||
|
|
<label className="text-xs font-semibold text-gray-600 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-[#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"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="space-y-1.5">
|
||
|
|
<label className="text-xs font-semibold text-gray-600 uppercase tracking-widest">Experience Level</label>
|
||
|
|
<select
|
||
|
|
value={profile.experienceLevel}
|
||
|
|
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"
|
||
|
|
>
|
||
|
|
<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-600 uppercase tracking-widest">Madhab</label>
|
||
|
|
<select
|
||
|
|
value={profile.madhab}
|
||
|
|
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"
|
||
|
|
>
|
||
|
|
<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-[#D4AF37] text-[#0a0a0f] py-4 rounded-xl font-bold text-sm disabled:opacity-50 active:scale-[0.98] 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-[#111118] border border-gray-800/60 rounded-2xl px-5 py-4">
|
||
|
|
<div className="w-9 h-9 rounded-xl bg-emerald-500/10 flex items-center justify-center">
|
||
|
|
<Sparkles size={17} className="text-emerald-400" />
|
||
|
|
</div>
|
||
|
|
<div className="flex-1">
|
||
|
|
<p className="text-sm font-semibold text-white">Halal Monitor</p>
|
||
|
|
<p className="text-xs text-gray-600">Find mosques & halal food near you</p>
|
||
|
|
</div>
|
||
|
|
<ChevronRight size={16} className="text-gray-600" />
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Sign Out */}
|
||
|
|
<div className="mx-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-900/50 text-red-500 bg-red-500/5 rounded-2xl py-4 text-sm font-semibold active:bg-red-500/10 transition"
|
||
|
|
>
|
||
|
|
<LogOut size={16} /> Sign Out
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
function ProfileSkeleton() {
|
||
|
|
return (
|
||
|
|
<div className="min-h-dvh bg-[#0a0a0f] pb-24 animate-pulse">
|
||
|
|
<div className="px-5 pt-6 pb-5"><div className="h-7 w-20 bg-gray-800 rounded-xl" /></div>
|
||
|
|
<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="w-16 h-16 rounded-2xl bg-gray-800" />
|
||
|
|
<div className="space-y-2 flex-1">
|
||
|
|
<div className="h-5 w-32 bg-gray-800 rounded-lg" />
|
||
|
|
<div className="h-4 w-48 bg-gray-800 rounded-lg" />
|
||
|
|
<div className="h-6 w-20 bg-gray-800 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-[#111118] border border-gray-800/60 rounded-2xl p-4 h-24" />)}
|
||
|
|
</div>
|
||
|
|
<div className="mx-4 bg-[#111118] border border-gray-800/60 rounded-3xl p-5 h-64" />
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|