Fix pipeline config crashes, settings UX, invite roles, seed expertise tags

- Fix critical crash when clicking Edit on INTAKE stage configs: normalize
  DB fileRequirements shape (type/required → acceptedMimeTypes/isRequired),
  add null guard in getActiveCategoriesFromMimeTypes
- Fix config summary display for all stage types to handle seed data key
  mismatches (votingEnabled→juryVotingEnabled, minAssignmentsPerJuror→
  minLoadPerJuror, deterministic.rules→rules, etc.)
- Add AWARD_MASTER role to invite page dropdown and user router validations
- Restructure settings sidebar: Tags and Webhooks as direct links instead
  of nested tabs, remove redundant Quick Links section
- Seed 38 expertise tags across 7 categories (Marine Science, Technology,
  Policy, Conservation, Business, Education, Engineering)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 11:40:44 +01:00
parent ae0ac58547
commit c88f540633
7 changed files with 187 additions and 118 deletions

View File

@@ -72,7 +72,7 @@ import {
import { cn } from '@/lib/utils'
type Step = 'input' | 'preview' | 'sending' | 'complete'
type Role = 'SUPER_ADMIN' | 'PROGRAM_ADMIN' | 'JURY_MEMBER' | 'MENTOR' | 'OBSERVER'
type Role = 'SUPER_ADMIN' | 'PROGRAM_ADMIN' | 'AWARD_MASTER' | 'JURY_MEMBER' | 'MENTOR' | 'OBSERVER'
interface Assignment {
projectId: string
@@ -104,6 +104,7 @@ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
const ROLE_LABELS: Record<Role, string> = {
SUPER_ADMIN: 'Super Admin',
PROGRAM_ADMIN: 'Program Admin',
AWARD_MASTER: 'Award Master',
JURY_MEMBER: 'Jury Member',
MENTOR: 'Mentor',
OBSERVER: 'Observer',
@@ -276,6 +277,7 @@ export default function MemberInvitePage() {
// Fetch current user to check role
const { data: currentUser } = trpc.user.me.useQuery()
const isSuperAdmin = currentUser?.role === 'SUPER_ADMIN'
const isAdmin = isSuperAdmin || currentUser?.role === 'PROGRAM_ADMIN'
const bulkCreate = trpc.user.bulkCreate.useMutation({
onSuccess: () => {
@@ -406,14 +408,16 @@ export default function MemberInvitePage() {
? 'SUPER_ADMIN'
: rawRole === 'PROGRAM_ADMIN'
? 'PROGRAM_ADMIN'
: rawRole === 'MENTOR'
? 'MENTOR'
: rawRole === 'OBSERVER'
? 'OBSERVER'
: 'JURY_MEMBER'
: rawRole === 'AWARD_MASTER'
? 'AWARD_MASTER'
: rawRole === 'MENTOR'
? 'MENTOR'
: rawRole === 'OBSERVER'
? 'OBSERVER'
: 'JURY_MEMBER'
const isValidFormat = emailRegex.test(email)
const isDuplicate = email ? seenEmails.has(email) : false
const isUnauthorizedAdmin = (role === 'PROGRAM_ADMIN' || role === 'SUPER_ADMIN') && !isSuperAdmin
const isUnauthorizedAdmin = role === 'SUPER_ADMIN' && !isSuperAdmin
if (isValidFormat && !isDuplicate && email) seenEmails.add(email)
return {
email,
@@ -428,7 +432,7 @@ export default function MemberInvitePage() {
: isDuplicate
? 'Duplicate email'
: isUnauthorizedAdmin
? 'Only super admins can invite program admins'
? 'Only super admins can invite super admins'
: undefined,
}
})
@@ -449,7 +453,7 @@ export default function MemberInvitePage() {
const email = r.email.trim().toLowerCase()
const isValidFormat = emailRegex.test(email)
const isDuplicate = seenEmails.has(email)
const isUnauthorizedAdmin = r.role === 'PROGRAM_ADMIN' && !isSuperAdmin
const isUnauthorizedAdmin = r.role === 'SUPER_ADMIN' && !isSuperAdmin
if (isValidFormat && !isDuplicate) seenEmails.add(email)
return {
email,
@@ -464,7 +468,7 @@ export default function MemberInvitePage() {
: isDuplicate
? 'Duplicate email'
: isUnauthorizedAdmin
? 'Only super admins can invite program admins'
? 'Only super admins can invite super admins'
: undefined,
}
})
@@ -547,7 +551,7 @@ export default function MemberInvitePage() {
Add members individually or upload a CSV file
{isSuperAdmin && (
<span className="block mt-1 text-primary font-medium">
As a super admin, you can also invite program admins
As a super admin, you can also invite super admins
</span>
)}
</CardDescription>
@@ -658,11 +662,16 @@ export default function MemberInvitePage() {
Super Admin
</SelectItem>
)}
{isSuperAdmin && (
{isAdmin && (
<SelectItem value="PROGRAM_ADMIN">
Program Admin
</SelectItem>
)}
{isAdmin && (
<SelectItem value="AWARD_MASTER">
Award Master
</SelectItem>
)}
<SelectItem value="JURY_MEMBER">
Jury Member
</SelectItem>