← back to Dear Bubbe Nextjs
components/UserMenu.tsx
258 lines
'use client'
import { useState, useRef, useEffect } from 'react'
import { useSession, signOut } from 'next-auth/react'
import Link from 'next/link'
interface UserMenuProps {
onProfileClick?: () => void
}
export default function UserMenu({ onProfileClick }: UserMenuProps) {
const { data: session } = useSession()
const [isOpen, setIsOpen] = useState(false)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
const handleDeleteAccount = async () => {
setIsDeleting(true)
try {
const response = await fetch('/api/delete-account', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
if (response.ok) {
// Clear only auth-related items from localStorage
// Don't clear userProfile or other app data
localStorage.removeItem('nextauth.message')
localStorage.removeItem('nextauth.csrf')
localStorage.removeItem('nextauth.callback-url')
// Keep userProfile, yiddishEnabled, and other app settings
// Sign out and redirect
await signOut({ callbackUrl: '/' })
} else {
const error = await response.json()
alert(`Failed to delete account: ${error.error || 'Unknown error'}`)
}
} catch (error) {
console.error('Error deleting account:', error)
alert('Failed to delete account. Please try again.')
} finally {
setIsDeleting(false)
setShowDeleteConfirm(false)
}
}
if (!session) return null
const userInitial = session.user?.name?.[0]?.toUpperCase() ||
session.user?.email?.[0]?.toUpperCase() ||
'?'
return (
<div className="relative" ref={dropdownRef}>
{/* User Avatar Button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-3 py-2 rounded-lg bg-white/20 hover:bg-white/30 transition-colors"
aria-label="User menu"
>
<div className="w-8 h-8 rounded-full bg-amber-600 text-white flex items-center justify-center font-bold text-sm">
{userInitial}
</div>
<svg
className={`w-4 h-4 text-white transition-transform ${isOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{/* Dropdown Menu */}
{isOpen && (
<div className="absolute right-0 mt-2 w-64 bg-white rounded-xl shadow-lg overflow-hidden z-50">
{/* User Info Header */}
<div className="px-4 py-3 bg-amber-50 border-b border-amber-100">
<p className="text-sm font-medium text-gray-900">
{session.user?.name || 'Bubbeleh'}
</p>
<p className="text-xs text-gray-600">
{session.user?.email}
</p>
</div>
{/* Menu Items */}
<div className="py-2">
<button
onClick={() => {
setIsOpen(false)
if (onProfileClick) onProfileClick()
}}
className="flex items-center gap-3 w-full px-4 py-2 text-sm text-gray-700 hover:bg-amber-50 transition-colors text-left"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
My Profile
</button>
<Link
href="/messages"
onClick={() => setIsOpen(false)}
className="flex items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-amber-50 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
My Messages
</Link>
<Link
href="/settings"
onClick={() => setIsOpen(false)}
className="flex items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-amber-50 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Settings
</Link>
<div className="border-t border-gray-100 my-2"></div>
<Link
href="/terms"
onClick={() => setIsOpen(false)}
className="flex items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-amber-50 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
User Agreement
</Link>
<Link
href="/privacy"
onClick={() => setIsOpen(false)}
className="flex items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-amber-50 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
Privacy Policy
</Link>
<div className="border-t border-gray-100 my-2"></div>
<button
onClick={() => {
setIsOpen(false)
setShowDeleteConfirm(true)
}}
className="flex items-center gap-3 w-full px-4 py-2 text-sm text-red-600 hover:bg-red-50 transition-colors text-left"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete Account
</button>
<div className="border-t border-gray-100 my-2"></div>
<button
onClick={() => {
setIsOpen(false)
signOut()
}}
className="flex items-center gap-3 w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors text-left"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
Sign Out
</button>
</div>
</div>
)}
{/* Delete Account Confirmation Modal */}
{showDeleteConfirm && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[100] p-4">
<div className="bg-white rounded-xl shadow-2xl max-w-md w-full p-6">
<div className="text-center mb-6">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h3 className="text-xl font-bold text-gray-900 mb-2">
Oy vey! Are you sure, bubbeleh?
</h3>
<p className="text-gray-600 mb-4">
You're about to delete your account and all your data. This cannot be undone!
</p>
<p className="text-sm text-gray-500">
Bubbe will miss you! All your messages, profile information, and settings will be permanently deleted.
</p>
</div>
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<p className="text-sm text-red-800 font-semibold mb-2">
⚠️ This action will permanently delete:
</p>
<ul className="text-sm text-red-700 space-y-1">
<li>• Your profile and personal information</li>
<li>• All conversation history with Bubbe</li>
<li>• Your settings and preferences</li>
<li>• Any discount codes or offers</li>
</ul>
</div>
<div className="flex gap-3">
<button
onClick={() => setShowDeleteConfirm(false)}
disabled={isDeleting}
className="flex-1 px-4 py-3 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors font-medium disabled:opacity-50"
>
Cancel, I'll stay!
</button>
<button
onClick={handleDeleteAccount}
disabled={isDeleting}
className="flex-1 px-4 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
{isDeleting ? 'Deleting...' : 'Yes, Delete My Account'}
</button>
</div>
{isDeleting && (
<div className="mt-4 text-center">
<div className="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-red-600"></div>
<p className="text-sm text-gray-600 mt-2">Saying goodbye to Bubbe...</p>
</div>
)}
</div>
</div>
)}
</div>
)
}