feat: add multi-role editor to member detail page
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m42s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m42s
Adds an "Additional Roles" checkbox section below the primary Role dropdown. Admins can now grant a user multiple dashboard views (e.g., Observer + Jury Member) without changing their primary role. The roles array is saved alongside the primary role and used by the role switcher. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { UserAvatar } from '@/components/shared/user-avatar'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import {
|
||||
ArrowLeft,
|
||||
Save,
|
||||
@@ -137,6 +138,7 @@ export default function MemberDetailPage() {
|
||||
const [maxAssignments, setMaxAssignments] = useState<string>('')
|
||||
const [showSuperAdminConfirm, setShowSuperAdminConfirm] = useState(false)
|
||||
const [pendingSuperAdminRole, setPendingSuperAdminRole] = useState(false)
|
||||
const [additionalRoles, setAdditionalRoles] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
@@ -146,16 +148,19 @@ export default function MemberDetailPage() {
|
||||
setStatus(user.status)
|
||||
setExpertiseTags(user.expertiseTags || [])
|
||||
setMaxAssignments(user.maxAssignments?.toString() || '')
|
||||
setAdditionalRoles(user.roles?.filter((r: string) => r !== user.role) || [])
|
||||
}
|
||||
}, [user])
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const allRoles = [role, ...additionalRoles.filter((r) => r !== role)] as Array<'SUPER_ADMIN' | 'PROGRAM_ADMIN' | 'AWARD_MASTER' | 'JURY_MEMBER' | 'MENTOR' | 'OBSERVER' | 'APPLICANT' | 'AUDIENCE'>
|
||||
await updateUser.mutateAsync({
|
||||
id: userId,
|
||||
email: email || undefined,
|
||||
name: name || null,
|
||||
role: role as 'SUPER_ADMIN' | 'PROGRAM_ADMIN' | 'AWARD_MASTER' | 'JURY_MEMBER' | 'MENTOR' | 'OBSERVER' | 'APPLICANT' | 'AUDIENCE',
|
||||
role: role as typeof allRoles[number],
|
||||
roles: allRoles,
|
||||
status: status as 'NONE' | 'INVITED' | 'ACTIVE' | 'SUSPENDED',
|
||||
expertiseTags,
|
||||
maxAssignments: maxAssignments ? parseInt(maxAssignments) : null,
|
||||
@@ -622,6 +627,31 @@ export default function MemberDetailPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Additional Roles</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Grant additional dashboard access beyond the primary role
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{(['JURY_MEMBER', 'OBSERVER', 'MENTOR', 'AWARD_MASTER'] as const)
|
||||
.filter((r) => r !== role)
|
||||
.map((r) => (
|
||||
<label key={r} className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox
|
||||
checked={additionalRoles.includes(r)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setAdditionalRoles((prev) => [...prev, r])
|
||||
} else {
|
||||
setAdditionalRoles((prev) => prev.filter((x) => x !== r))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{r.replace(/_/g, ' ')}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="status">Status</Label>
|
||||
<Select value={status} onValueChange={setStatus}>
|
||||
|
||||
Reference in New Issue
Block a user