feat: observer UX overhaul — reports, projects, charts, session & email
All checks were successful
Build and Push Docker Image / build (push) Successful in 11m2s

- Observer projects: default sort by status (rejected last), sortable status column
- Observer projects: search by country, institution, geographic zone
- Observer project detail: vertical timeline connectors between rounds
- Fix React key warning in ExpandableJurorTable and FilteringReportTabs
- Fix ScoreBadge text always white for better contrast on all backgrounds
- Remove misleading /30 denominator from heatmap juror reviewed count
- INTAKE stats: show Start-ups, Business Concepts, Countries (not States/Categories)
- DiversityMetrics: extractCountry() for country-only display in charts
- Fix nested button hydration error in filtering report mobile view
- Color project titles by outcome in filtering report (green/red/amber)
- Redesign CrossStageComparisonChart: funnel viz + metrics table with attrition %
- Center doughnut chart in StatusBreakdownChart
- Remove redundant RoundTypeStatsCards from evaluation report
- Move evaluation tab bar below overview header, rename to "Juror Assignments"
- Dev email override system (DEV_EMAIL_OVERRIDE env var)
- Session refresh on role change without re-login
- Role switcher in user dropdown menu
- formatCategory() utility for consistent category display
- Activity feed max height constraint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 13:37:50 +01:00
parent e7b99fff63
commit a556732b46
23 changed files with 2108 additions and 326 deletions

View File

@@ -283,9 +283,9 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
}
// On session update, handle impersonation or normal refresh
if (trigger === 'update' && session) {
if (trigger === 'update') {
// Start impersonation
if (session.impersonate && typeof session.impersonate === 'string') {
if (session?.impersonate && typeof session.impersonate === 'string') {
// Only SUPER_ADMIN can impersonate (defense-in-depth)
if (token.role === 'SUPER_ADMIN' && !token.impersonating) {
const targetUser = await prisma.user.findUnique({
@@ -311,7 +311,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
}
}
// End impersonation
else if (session.endImpersonation && token.impersonating) {
else if (session?.endImpersonation && token.impersonating) {
const original = token.impersonating as { originalId: string; originalRole: UserRole; originalRoles: UserRole[]; originalEmail: string }
token.id = original.originalId
token.role = original.originalRole