Add file requirements per round and super admin promotion via UI
Part A: File Requirements per Round - New FileRequirement model with name, description, accepted MIME types, max size, required flag, sort order - Added requirementId FK to ProjectFile for linking uploads to requirements - Backend CRUD (create/update/delete/reorder) in file router with audit logging - Mime type validation and team member upload authorization in applicant router - Admin UI: FileRequirementsEditor component in round edit page - Applicant UI: RequirementUploadSlot/List components in submission detail and team pages - Viewer UI: RequirementChecklist with fulfillment status in file-viewer Part B: Super Admin Promotion - Added SUPER_ADMIN to role enums in user create/update/bulkCreate with guards - Member detail page: SUPER_ADMIN dropdown option with AlertDialog confirmation - Invite page: SUPER_ADMIN option visible only to super admins Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,16 @@ import {
|
||||
import { toast } from 'sonner'
|
||||
import { TagInput } from '@/components/shared/tag-input'
|
||||
import { UserActivityLog } from '@/components/shared/user-activity-log'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import {
|
||||
ArrowLeft,
|
||||
Save,
|
||||
@@ -51,6 +61,8 @@ export default function MemberDetailPage() {
|
||||
const userId = params.id as string
|
||||
|
||||
const { data: user, isLoading, error, refetch } = trpc.user.get.useQuery({ id: userId })
|
||||
const { data: currentUser } = trpc.user.me.useQuery()
|
||||
const isSuperAdmin = currentUser?.role === 'SUPER_ADMIN'
|
||||
const updateUser = trpc.user.update.useMutation()
|
||||
const sendInvitation = trpc.user.sendInvitation.useMutation()
|
||||
|
||||
@@ -65,6 +77,8 @@ export default function MemberDetailPage() {
|
||||
const [status, setStatus] = useState<string>('INVITED')
|
||||
const [expertiseTags, setExpertiseTags] = useState<string[]>([])
|
||||
const [maxAssignments, setMaxAssignments] = useState<string>('')
|
||||
const [showSuperAdminConfirm, setShowSuperAdminConfirm] = useState(false)
|
||||
const [pendingSuperAdminRole, setPendingSuperAdminRole] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
@@ -81,7 +95,7 @@ export default function MemberDetailPage() {
|
||||
await updateUser.mutateAsync({
|
||||
id: userId,
|
||||
name: name || null,
|
||||
role: role as 'JURY_MEMBER' | 'MENTOR' | 'OBSERVER' | 'PROGRAM_ADMIN',
|
||||
role: role as 'SUPER_ADMIN' | 'JURY_MEMBER' | 'MENTOR' | 'OBSERVER' | 'PROGRAM_ADMIN',
|
||||
status: status as 'INVITED' | 'ACTIVE' | 'SUSPENDED',
|
||||
expertiseTags,
|
||||
maxAssignments: maxAssignments ? parseInt(maxAssignments) : null,
|
||||
@@ -211,15 +225,28 @@ export default function MemberDetailPage() {
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="role">Role</Label>
|
||||
<Select value={role} onValueChange={setRole}>
|
||||
<Select
|
||||
value={role}
|
||||
onValueChange={(v) => {
|
||||
if (v === 'SUPER_ADMIN') {
|
||||
setPendingSuperAdminRole(true)
|
||||
setShowSuperAdminConfirm(true)
|
||||
} else {
|
||||
setRole(v)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger id="role">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{isSuperAdmin && (
|
||||
<SelectItem value="SUPER_ADMIN">Super Admin</SelectItem>
|
||||
)}
|
||||
<SelectItem value="PROGRAM_ADMIN">Program Admin</SelectItem>
|
||||
<SelectItem value="JURY_MEMBER">Jury Member</SelectItem>
|
||||
<SelectItem value="MENTOR">Mentor</SelectItem>
|
||||
<SelectItem value="OBSERVER">Observer</SelectItem>
|
||||
<SelectItem value="PROGRAM_ADMIN">Program Admin</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -377,6 +404,39 @@ export default function MemberDetailPage() {
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Super Admin Confirmation Dialog */}
|
||||
<AlertDialog open={showSuperAdminConfirm} onOpenChange={setShowSuperAdminConfirm}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Grant Super Admin Access?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will grant <strong>{name || user?.name || 'this user'}</strong> full Super Admin
|
||||
access, including user management, system settings, and all administrative
|
||||
capabilities. This action should only be performed for trusted administrators.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel
|
||||
onClick={() => {
|
||||
setPendingSuperAdminRole(false)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
setRole('SUPER_ADMIN')
|
||||
setPendingSuperAdminRole(false)
|
||||
setShowSuperAdminConfirm(false)
|
||||
}}
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
>
|
||||
Confirm Super Admin
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user