- Refactored SettingsModal to a slide-in SettingsSidebar - Added comprehensive preference toggles (Someday, All-Day, Schedule) - Integrated Google Fonts selection (Headline/Body/Weight) - Synchronized all preferences with DB and WeeklyView state - Fixed sidebar positioning and overlay CSS
85 lines
4.6 KiB
TypeScript
85 lines
4.6 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import { signOut } from 'next-auth/react';
|
|
|
|
interface UserMenuProps {
|
|
userEmail?: string | null;
|
|
onOpenSettings: () => void;
|
|
trigger?: React.ReactNode;
|
|
}
|
|
|
|
export default function UserMenu({ userEmail, onOpenSettings, trigger }: UserMenuProps) {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="relative" ref={menuRef}>
|
|
{trigger ? (
|
|
<div onClick={() => setIsOpen(!isOpen)}>{trigger}</div>
|
|
) : (
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center hover:bg-gray-300 transition-colors"
|
|
title="User Menu"
|
|
>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-gray-600">
|
|
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
|
<circle cx="12" cy="7" r="4"></circle>
|
|
</svg>
|
|
</button>
|
|
)}
|
|
|
|
{isOpen && (
|
|
<div className="absolute right-0 mt-2 w-56 bg-white rounded-lg shadow-xl border border-gray-100 py-1 z-50 animate-in fade-in slide-in-from-top-1 duration-200">
|
|
<div className="px-4 py-2 border-b border-gray-100">
|
|
<p className="text-sm font-medium text-gray-900">Signed in as</p>
|
|
<p className="text-xs text-gray-500 truncate">{userEmail || 'User'}</p>
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => { onOpenSettings(); setIsOpen(false); }}
|
|
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
|
|
>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-gray-400">
|
|
<circle cx="12" cy="12" r="3"></circle>
|
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
|
</svg>
|
|
Settings
|
|
</button>
|
|
|
|
|
|
<div className="border-t border-gray-100 my-1"></div>
|
|
|
|
<button
|
|
onClick={() => signOut()}
|
|
className="w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-50 flex items-center gap-2"
|
|
>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-red-400">
|
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
|
|
<polyline points="16 17 21 12 16 7"></polyline>
|
|
<line x1="21" y1="12" x2="9" y2="12"></line>
|
|
</svg>
|
|
Sign Out
|
|
</button>
|
|
</div>
|
|
)}
|
|
<style jsx>{`
|
|
.animate-in { animation: fadeIn 0.15s ease-out; }
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(-5px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|