diff --git a/src/components/layouts/applicant-nav.tsx b/src/components/layouts/applicant-nav.tsx index dbbeaa9..227e838 100644 --- a/src/components/layouts/applicant-nav.tsx +++ b/src/components/layouts/applicant-nav.tsx @@ -16,6 +16,8 @@ export function ApplicantNav({ user }: ApplicantNavProps) { staleTime: 60_000, }) + const useExternal = featureFlags?.learningHubExternal && featureFlags.learningHubExternalUrl + const navigation: NavItem[] = [ { name: 'Dashboard', href: '/applicant', icon: Home }, { name: 'Project', href: '/applicant/team', icon: FolderOpen }, @@ -27,7 +29,12 @@ export function ApplicantNav({ user }: ApplicantNavProps) { ...(flags?.hasMentor ? [{ name: 'Mentoring', href: '/applicant/mentor', icon: MessageSquare }] : []), - { name: 'Resources', href: '/applicant/resources', icon: BookOpen }, + { + name: 'Learning Hub', + href: useExternal ? featureFlags.learningHubExternalUrl : '/applicant/resources', + icon: BookOpen, + external: !!useExternal, + }, ] return ( diff --git a/src/components/layouts/role-nav.tsx b/src/components/layouts/role-nav.tsx index 647a266..0e973d2 100644 --- a/src/components/layouts/role-nav.tsx +++ b/src/components/layouts/role-nav.tsx @@ -71,13 +71,31 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge, edi const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) const { data: session, status: sessionStatus, update: updateSession } = useSession() const isAuthenticated = sessionStatus === 'authenticated' + const isImpersonating = !!session?.user?.impersonating const { data: avatarUrl } = trpc.avatar.getUrl.useQuery(undefined, { enabled: isAuthenticated, }) + const endImpersonation = trpc.user.endImpersonation.useMutation() + const logNavClick = trpc.learningResource.logNavClick.useMutation() const { theme, setTheme } = useTheme() const [mounted, setMounted] = useState(false) useEffect(() => setMounted(true), []) + const handleSignOut = async () => { + if (isImpersonating) { + try { + await endImpersonation.mutateAsync() + await updateSession({ endImpersonation: true }) + window.location.href = '/admin/members' + } catch { + // Fallback: just sign out completely + signOut({ callbackUrl: '/login' }) + } + return + } + signOut({ callbackUrl: '/login' }) + } + // Auto-refresh session on mount to pick up role changes without requiring re-login useEffect(() => { if (isAuthenticated) { @@ -115,7 +133,14 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge, edi ) if (item.external) { return ( - + logNavClick.mutate({ url: item.href })} + > {item.name} @@ -211,11 +236,11 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge, edi )} signOut({ callbackUrl: '/login' })} + onClick={handleSignOut} className="text-destructive focus:text-destructive" > - Sign Out + {isImpersonating ? 'Return to Admin' : 'Sign Out'} @@ -257,7 +282,14 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge, edi ) if (item.external) { return ( - setIsMobileMenuOpen(false)} className={className}> + { logNavClick.mutate({ url: item.href }); setIsMobileMenuOpen(false) }} + className={className} + > {item.name} @@ -299,10 +331,10 @@ export function RoleNav({ navigation, roleName, user, basePath, statusBadge, edi diff --git a/src/server/routers/learningResource.ts b/src/server/routers/learningResource.ts index ebefb45..8ecfbf6 100644 --- a/src/server/routers/learningResource.ts +++ b/src/server/routers/learningResource.ts @@ -271,6 +271,24 @@ export const learningResourceRouter = router({ return { success: true } }), + /** + * Log when a user clicks the Learning Hub nav link (especially external). + * Creates an audit log entry so admins can see who accessed it. + */ + logNavClick: protectedProcedure + .input(z.object({ url: z.string() })) + .mutation(async ({ ctx, input }) => { + await ctx.prisma.auditLog.create({ + data: { + userId: ctx.user.id, + action: 'LEARNING_HUB_CLICK', + entityType: 'LearningResource', + detailsJson: { url: input.url, role: ctx.user.role }, + }, + }).catch(() => {}) + return { success: true } + }), + /** * Create a new resource (admin only) */