feat: Implement guest menu in the common header for unauthenticated users and enhance JWT session handling for user profile updates.
This commit is contained in:
@@ -145,7 +145,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
return true;
|
||||
},
|
||||
|
||||
async jwt({ token, user }) {
|
||||
async jwt({ token, user, trigger, session: updateData }) {
|
||||
if (user) {
|
||||
token.id = user.id;
|
||||
token.name = user.name;
|
||||
@@ -158,6 +158,12 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
token.accessTokenIssuedAt = Date.now();
|
||||
}
|
||||
|
||||
// Handle session updates from client-side updateSession() calls
|
||||
if (trigger === "update" && updateData?.user) {
|
||||
if (updateData.user.name) token.name = updateData.user.name;
|
||||
if (updateData.user.image !== undefined) token.picture = updateData.user.image;
|
||||
}
|
||||
|
||||
// Auto-refresh the access token if it's about to expire (refresh 2 minutes before expiry)
|
||||
// Backend JWT_ACCESS_EXPIRATION=15m, so refresh at ~13 minutes
|
||||
const ACCESS_TOKEN_MAX_AGE = 13 * 60 * 1000; // 13 minutes in ms
|
||||
|
||||
@@ -19,8 +19,10 @@ const navLinks = [
|
||||
export function CommonHeader() {
|
||||
const { data: session } = useSession();
|
||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||
const [showGuestMenu, setShowGuestMenu] = useState(false);
|
||||
const [showMobileMenu, setShowMobileMenu] = useState(false);
|
||||
const profileMenuRef = useRef<HTMLDivElement>(null);
|
||||
const guestMenuRef = useRef<HTMLDivElement>(null);
|
||||
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
||||
const [profileImage, setProfileImage] = useState<string | null>(null);
|
||||
const [profileName, setProfileName] = useState<string | null>(null);
|
||||
@@ -32,19 +34,22 @@ export function CommonHeader() {
|
||||
if (profileMenuRef.current && !profileMenuRef.current.contains(event.target as Node)) {
|
||||
setShowProfileMenu(false);
|
||||
}
|
||||
if (guestMenuRef.current && !guestMenuRef.current.contains(event.target as Node)) {
|
||||
setShowGuestMenu(false);
|
||||
}
|
||||
if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target as Node)) {
|
||||
setShowMobileMenu(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (showProfileMenu || showMobileMenu) {
|
||||
if (showProfileMenu || showGuestMenu || showMobileMenu) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [showProfileMenu, showMobileMenu]);
|
||||
}, [showProfileMenu, showGuestMenu, showMobileMenu]);
|
||||
|
||||
// Fetch profile data (image and name) from backend based on user role
|
||||
const fetchProfileData = useCallback(async () => {
|
||||
@@ -292,14 +297,83 @@ export function CommonHeader() {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Link href="/login" className="hover:opacity-80 transition-opacity">
|
||||
<Image
|
||||
src="/assets/icons/profile-circle-orange-icon.svg"
|
||||
alt="Login"
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
</Link>
|
||||
<div className="relative" ref={guestMenuRef}>
|
||||
<button
|
||||
onClick={() => setShowGuestMenu(!showGuestMenu)}
|
||||
className="flex items-center gap-2 md:gap-3 hover:opacity-80 transition-opacity cursor-pointer"
|
||||
>
|
||||
<Image
|
||||
src="/assets/icons/signin-user-icon.svg"
|
||||
alt="Sign in"
|
||||
width={20}
|
||||
height={22}
|
||||
/>
|
||||
<div className="hidden sm:flex flex-col text-left">
|
||||
<span className="font-fractul font-bold text-[14px] text-[#e58625] leading-tight">
|
||||
Sign in
|
||||
</span>
|
||||
<span className="font-fractul font-bold text-[10px] text-[#00293d] leading-tight">
|
||||
Sign into connect
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{showGuestMenu && (
|
||||
<div className="absolute right-0 top-full mt-3 w-[271px] bg-white rounded-[15px] border border-black/20 z-50 shadow-[0px_4px_4px_0px_rgba(0,0,0,0.25)]">
|
||||
<div className="absolute -top-2 right-6 w-0 h-0 border-l-[8px] border-l-transparent border-r-[8px] border-r-transparent border-b-[8px] border-b-white"></div>
|
||||
|
||||
<div className="flex flex-col items-center pt-6 pb-4 border-b border-black/10">
|
||||
<Image
|
||||
src="/assets/icons/signin-user-icon.svg"
|
||||
alt="User"
|
||||
width={36}
|
||||
height={40}
|
||||
/>
|
||||
<p className="font-serif text-[14px] text-black/50 mt-3">
|
||||
Sign in or create account to continue
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/login"
|
||||
className="flex items-center gap-3 px-4 py-3 border-b border-black/10 hover:bg-black/5 transition-colors"
|
||||
onClick={() => setShowGuestMenu(false)}
|
||||
>
|
||||
<Image src="/assets/icons/settings-icon.svg" alt="Settings" width={24} height={24} />
|
||||
<div className="flex flex-col">
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">Account Settings</p>
|
||||
<p className="font-serif text-[14px] leading-[18px] text-black/50">Profile, Security</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/login"
|
||||
className="flex items-center gap-3 px-4 py-3 border-b border-black/10 hover:bg-black/5 transition-colors"
|
||||
onClick={() => setShowGuestMenu(false)}
|
||||
>
|
||||
<Image src="/assets/icons/notification-settings-icon.svg" alt="Notifications" width={24} height={24} />
|
||||
<p className="font-fractul text-[16px] leading-[18px] text-black">Notification Settings</p>
|
||||
</Link>
|
||||
|
||||
<div className="flex gap-3 px-4 py-4">
|
||||
<Link
|
||||
href="/signup"
|
||||
onClick={() => setShowGuestMenu(false)}
|
||||
className="flex-1 h-[37px] bg-[#e58625] rounded-[15px] flex items-center justify-center font-fractul text-[14px] text-white hover:bg-[#d47920] transition-colors"
|
||||
>
|
||||
Sign up
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
onClick={() => setShowGuestMenu(false)}
|
||||
className="flex-1 h-[37px] border border-[#e58625] rounded-[15px] flex items-center justify-center font-fractul text-[14px] text-[#e58625] hover:bg-[#e58625]/5 transition-colors"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function SpecializationSection({ fieldsData }: SpecializationSectionProps
|
||||
|
||||
{/* Top row - up to 3 cards */}
|
||||
{topRowFields.length > 0 && (
|
||||
<div className={`flex flex-col lg:grid lg:items-start gap-6 mb-6 ${
|
||||
<div className={`flex flex-col lg:grid lg:items-stretch gap-6 mb-6 ${
|
||||
topRowFields.length === 1 ? 'lg:grid-cols-1 lg:max-w-md lg:mx-auto' :
|
||||
topRowFields.length === 2 ? 'lg:grid-cols-2 lg:max-w-2xl lg:mx-auto' :
|
||||
'lg:grid-cols-3'
|
||||
@@ -54,7 +54,7 @@ export function SpecializationSection({ fieldsData }: SpecializationSectionProps
|
||||
|
||||
{/* Bottom row - remaining cards */}
|
||||
{bottomRowFields.length > 0 && (
|
||||
<div className={`flex flex-col lg:grid lg:items-start gap-6 ${
|
||||
<div className={`flex flex-col lg:grid lg:items-stretch gap-6 ${
|
||||
bottomRowFields.length === 1 ? 'lg:grid-cols-1 lg:max-w-md lg:mx-auto' :
|
||||
bottomRowFields.length === 2 ? 'lg:grid-cols-2 lg:max-w-2xl lg:mx-auto' :
|
||||
'lg:grid-cols-3'
|
||||
|
||||
Reference in New Issue
Block a user