'use client'
import { useState, useEffect, useCallback } from 'react'
import { useAuth } from '@/components/AuthProvider'
import { useProfile } from '@/lib/hooks/useProfile'
import { useSubscription } from '@/lib/hooks/useSubscription'
import { useWelcomeTour } from '@/lib/hooks/useWelcomeTour'
import DashboardLayout from '@/components/dashboard/DashboardLayout'
import ProfileEditDialog from '@/components/ProfileEditDialog'
import WelcomeTour from '@/components/WelcomeTour'
import {
SparklesIcon,
ArrowRightOnRectangleIcon,
PencilIcon,
UserCircleIcon,
AcademicCapIcon,
} from '@heroicons/react/24/outline'
import { formatDate, formatPrice } from '@/lib/stripe'
import { supabase } from '@/lib/supabase'
import Image from 'next/image'
interface EmailPreferences {
weekly_progress_digest: boolean
product_updates: boolean
account_notifications: boolean
}
export default function ProfilePage() {
const { user, signingOut, signOut } = useAuth()
const { profile } = useProfile()
const {
subscription,
isActive,
getCurrentPlan,
canManageBilling
} = useSubscription()
const [emailPreferences, setEmailPreferences] = useState<EmailPreferences>({
weekly_progress_digest: true,
product_updates: true,
account_notifications: true
})
const [emailSaving, setEmailSaving] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const { openTour, isOpen: isTourOpen, closeTour, completeTour } = useWelcomeTour()
const currentPlan = getCurrentPlan()
const fetchEmailPreferences = useCallback(async () => {
try {
const response = await fetch(`/api/profile/email-preferences?userId=${user?.id}`, {
headers: {
'Authorization': `Bearer ${(await supabase.auth.getSession()).data.session?.access_token}`
}
})
if (response.ok) {
const data = await response.json()
setEmailPreferences(data.preferences)
}
} catch (error) {
console.error('Error fetching email preferences:', error)
}
}, [user?.id])
useEffect(() => {
if (user?.id) {
fetchEmailPreferences()
}
}, [user?.id, fetchEmailPreferences])
const updateEmailPreferences = async (newPreferences: EmailPreferences) => {
try {
setEmailSaving(true)
const response = await fetch('/api/profile/email-preferences', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${(await supabase.auth.getSession()).data.session?.access_token}`
},
body: JSON.stringify({
userId: user?.id,
...newPreferences
}),
})
if (response.ok) {
setEmailPreferences(newPreferences)
}
} catch (error) {
console.error('Error updating email preferences:', error)
} finally {
setEmailSaving(false)
}
}
const handleEmailPreferenceChange = (key: keyof EmailPreferences, value: boolean) => {
const newPreferences = { ...emailPreferences, [key]: value }
updateEmailPreferences(newPreferences)
}
const getInitials = () => {
// Use profile data first, then fallback to auth metadata
const fullName = profile?.full_name || user?.user_metadata?.full_name
if (fullName) {
return fullName
.split(' ')
.map((name: string) => name[0])
.join('')
.toUpperCase()
.slice(0, 2)
}
return user?.email?.slice(0, 2).toUpperCase() || '??'
}
const getAvatarUrl = () => {
// Use profile avatar first, then fallback to auth metadata
const profileAvatar = profile?.avatar_url
const authAvatar = user?.user_metadata?.avatar_url || user?.user_metadata?.picture
// Only return a URL if it's not empty/null/undefined
if (profileAvatar && profileAvatar.trim() !== '') {
return profileAvatar
}
if (authAvatar && authAvatar.trim() !== '') {
return authAvatar
}
return null
}
const getDisplayName = () => {
// Use profile data first, then fallback to auth metadata
return profile?.full_name || user?.user_metadata?.full_name || 'Your Profile'
}
const getLoginProvider = () => {
// Check app_metadata for provider info
const appMetadata = user?.app_metadata
const userMetadata = user?.user_metadata
// Provider is usually in app_metadata.provider
if (appMetadata?.provider) {
return appMetadata.provider
}
// If no explicit provider, try to infer from metadata structure
if (userMetadata?.picture && userMetadata?.picture.includes('googleusercontent.com')) {
return 'google'
}
if (userMetadata?.avatar_url && userMetadata?.avatar_url.includes('avatars.githubusercontent.com')) {
return 'github'
}
// Fallback to checking identities
if (user?.identities && user.identities.length > 0) {
return user.identities[0].provider
}
return 'email'
}
const getProviderIcon = (provider: string) => {
switch (provider) {
case 'google':
return (
<div className="w-4 h-4 bg-white rounded-full flex items-center justify-center">
<svg viewBox="0 0 24 24" className="w-2.5 h-2.5">
<path fill="#4285f4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34a853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#fbbc05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#ea4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
</div>
)
case 'github':
return (
<div className="w-4 h-4 bg-gray-900 rounded-full flex items-center justify-center">
<svg viewBox="0 0 24 24" className="w-2.5 h-2.5 fill-white">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
</div>
)
default:
return <UserCircleIcon className="w-4 h-4 text-gray-400" />
}
}
const getProviderName = (provider: string) => {
switch (provider) {
case 'google': return 'Google'
case 'github': return 'GitHub'
case 'email': return 'Email'
default: return provider.charAt(0).toUpperCase() + provider.slice(1)
}
}
return (
<DashboardLayout
title="Profile"
subtitle="Account settings and preferences"
>
<div className="space-y-6">
{/* Profile & Subscription */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Profile Info */}
<div className="bg-gray-900/40 backdrop-blur-sm border border-gray-800/50 rounded-2xl p-6">
<div className="flex items-center space-x-4 mb-6">
<div className="flex-shrink-0 relative">
{getAvatarUrl() ? (
<>
<Image
src={getAvatarUrl()}
alt="Profile"
width={48}
height={48}
className="w-12 h-12 rounded-xl object-cover border-2 border-white/20"
onError={(e) => {
console.error('Avatar failed to load:', getAvatarUrl())
// Hide the image and show the fallback
e.currentTarget.style.display = 'none'
// Show the fallback div
const fallback = e.currentTarget.nextElementSibling as HTMLElement
if (fallback) fallback.style.display = 'flex'
}}
/>
<div
className="hidden w-12 h-12 rounded-xl bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 items-center justify-center border-2 border-white/20"
style={{ display: 'none' }}
>
<span className="text-white font-black text-sm">{getInitials()}</span>
</div>
</>
) : (
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 flex items-center justify-center border-2 border-white/20">
<span className="text-white font-black text-sm">{getInitials()}</span>
</div>
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="text-lg font-bold text-white truncate">
{getDisplayName()}
</h3>
<div className="flex items-center gap-2 mb-1">
<p className="text-gray-400 text-sm truncate">{user?.email}</p>
<div className="flex items-center gap-1 px-2 py-1 bg-gray-800/50 rounded text-xs">
{getProviderIcon(getLoginProvider())}
<span className="text-gray-300">{getProviderName(getLoginProvider())}</span>
</div>
</div>
<p className="text-gray-500 text-xs">
Member since {formatDate(user?.created_at || '')}
</p>
</div>
<button
onClick={() => setIsEditDialogOpen(true)}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-colors"
title="Edit Profile"
>
<PencilIcon className="w-4 h-4" />
</button>
</div>
<div className="flex gap-2">
<button
onClick={openTour}
className="flex-1 flex items-center justify-center p-2.5 bg-gray-800/50 hover:bg-gray-700/50 border border-gray-700/50 rounded-lg transition-all text-gray-300 hover:text-white text-sm"
>
<AcademicCapIcon className="w-4 h-4 mr-1.5" />
Tour
</button>
<button
onClick={signOut}
disabled={signingOut}
className="flex-1 flex items-center justify-center p-2.5 bg-gray-800/50 hover:bg-gray-700/50 border border-gray-700/50 rounded-lg transition-all text-gray-300 hover:text-white text-sm disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-gray-800/50"
>
{signingOut ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-2 border-gray-300 border-t-transparent mr-1.5"></div>
Signing Out...
</>
) : (
<>
<ArrowRightOnRectangleIcon className="w-4 h-4 mr-1.5" />
Log Out
</>
)}
</button>
</div>
</div>
{/* Subscription */}
<div className="bg-gray-900/40 backdrop-blur-sm border border-gray-800/50 rounded-2xl p-6">
<h3 className="text-lg font-bold text-white mb-4">Subscription</h3>
<div className="flex items-center space-x-4 mb-4">
<div className="p-3 bg-gradient-to-br from-teal-500/20 to-cyan-500/20 rounded-lg border border-teal-500/30">
<SparklesIcon className="h-6 w-6 text-teal-400" />
</div>
<div className="flex-1">
<h4 className="text-white font-medium">
{currentPlan ? currentPlan.name : 'Free Plan'}
</h4>
<p className="text-gray-400 text-sm">
{isActive ? 'Active' : 'No subscription'}
</p>
</div>
{currentPlan && (
<div className="text-right">
<span className="text-lg font-bold text-white">
{formatPrice(currentPlan.pricing.monthly.price)}
</span>
<span className="text-gray-400 text-sm">/mo</span>
</div>
)}
</div>
{canManageBilling() ? (
<a
href="/dashboard/billing"
className="w-full block text-center p-2.5 text-sm font-medium text-gray-300 hover:text-white bg-gray-800/50 hover:bg-gray-700/50 border border-gray-700/50 rounded-lg transition-all"
>
Manage Subscription
</a>
) : (
<a
href="/pricing"
className="w-full block text-center p-2.5 text-sm font-medium text-gray-300 hover:text-white bg-gray-800/50 hover:bg-gray-700/50 border border-gray-700/50 rounded-lg transition-all"
>
Upgrade Plan
</a>
)}
</div>
</div>
{/* Email Preferences */}
<div className="bg-gray-900/40 backdrop-blur-sm border border-gray-800/50 rounded-2xl p-6">
<h3 className="text-lg font-bold text-white mb-4">Email Preferences</h3>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<span className="text-white text-sm font-medium">Weekly Progress</span>
<p className="text-gray-400 text-xs">Get weekly writing summaries</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={emailPreferences.weekly_progress_digest}
onChange={(e) => handleEmailPreferenceChange('weekly_progress_digest', e.target.checked)}
disabled={emailSaving}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-teal-600"></div>
</label>
</div>
<div className="flex items-center justify-between">
<div>
<span className="text-white text-sm font-medium">Product Updates</span>
<p className="text-gray-400 text-xs">New features and improvements</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={emailPreferences.product_updates}
onChange={(e) => handleEmailPreferenceChange('product_updates', e.target.checked)}
disabled={emailSaving}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-teal-600"></div>
</label>
</div>
<div className="flex items-center justify-between">
<div>
<span className="text-white text-sm font-medium">Account Notifications</span>
<p className="text-gray-400 text-xs">Important account updates</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={emailPreferences.account_notifications}
onChange={(e) => handleEmailPreferenceChange('account_notifications', e.target.checked)}
disabled={emailSaving}
className="sr-only peer"
/>
<div className="w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-teal-600"></div>
</label>
</div>
</div>
{emailSaving && (
<div className="mt-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
<p className="text-blue-300 text-sm flex items-center">
<div className="animate-spin rounded-full h-3 w-3 border-2 border-blue-500 border-t-transparent mr-2"></div>
Saving...
</p>
</div>
)}
</div>
</div>
{/* Profile Edit Dialog */}
<ProfileEditDialog
isOpen={isEditDialogOpen}
onClose={() => setIsEditDialogOpen(false)}
/>
{/* Welcome Tour */}
<WelcomeTour
isOpen={isTourOpen}
onClose={() => closeTour('skipped')}
onComplete={completeTour}
/>
</DashboardLayout>
)
}