@@ -127,6 +158,10 @@
},
{ id: 'copy-id', action: () => copyId() },
{ id: 'copy-permalink', action: () => copyPermalink() },
+ {
+ divider: true,
+ shown: auth.user && isAdmin(auth.user),
+ },
{
id: 'open-billing',
action: () => navigateTo(`/admin/billing/${user.id}`),
@@ -137,6 +172,11 @@
action: () => $refs.userDetailsModal.show(),
shown: auth.user && isStaff(auth.user),
},
+ {
+ id: 'edit-role',
+ action: () => openRoleEditModal(),
+ shown: auth.user && isAdmin(auth.user),
+ },
]"
aria-label="More options"
>
@@ -165,6 +205,10 @@
{{ formatMessage(messages.infoButton) }}
+
+
+ {{ formatMessage(messages.editRoleButton) }}
+
@@ -355,6 +399,8 @@ import {
LockIcon,
MoreVerticalIcon,
ReportIcon,
+ SaveIcon,
+ SpinnerIcon,
XIcon,
} from '@modrinth/assets'
import {
@@ -362,10 +408,13 @@ import {
ButtonStyled,
commonMessages,
ContentPageHeader,
+ injectNotificationManager,
NewModal,
OverflowMenu,
+ TeleportDropdownMenu,
useRelativeTime,
} from '@modrinth/ui'
+import { isAdmin } from '@modrinth/utils'
import { IntlFormatted } from '@vintl/vintl/components'
import TenMClubBadge from '~/assets/images/badges/10m-club.svg?component'
@@ -398,6 +447,8 @@ const formatCompactNumber = useCompactNumber(true)
const formatRelativeTime = useRelativeTime()
+const { addNotification } = injectNotificationManager()
+
const messages = defineMessages({
profileProjectsStats: {
id: 'profile.stats.projects',
@@ -472,6 +523,10 @@ const messages = defineMessages({
id: 'profile.button.info',
defaultMessage: 'View user details',
},
+ editRoleButton: {
+ id: 'profile.button.edit-role',
+ defaultMessage: 'Edit role',
+ },
userNotFoundError: {
id: 'profile.error.not-found',
defaultMessage: 'User not found',
@@ -648,6 +703,55 @@ const navLinks = computed(() => [
.slice()
.sort((a, b) => a.label.localeCompare(b.label)),
])
+
+const selectedRole = ref(user.value.role)
+const isSavingRole = ref(false)
+
+const roleOptions = ['developer', 'moderator', 'admin']
+
+const editRoleModal = useTemplateRef('editRoleModal')
+
+const openRoleEditModal = () => {
+ selectedRole.value = user.value.role
+ editRoleModal.value?.show()
+}
+
+const cancelRoleEdit = () => {
+ selectedRole.value = user.value.role
+ editRoleModal.value?.hide()
+}
+
+function saveRoleEdit() {
+ if (!selectedRole.value || selectedRole.value === user.value.role) {
+ return
+ }
+
+ isSavingRole.value = true
+
+ useBaseFetch(`user/${user.value.id}`, {
+ method: 'PATCH',
+ body: {
+ role: selectedRole.value,
+ },
+ })
+ .then(() => {
+ user.value.role = selectedRole.value
+
+ editRoleModal.value?.hide()
+ })
+ .catch(() => {
+ console.error('Failed to update user role:', error)
+
+ addNotification({
+ type: 'error',
+ title: 'Failed to update role',
+ message: 'An error occurred while updating the user role. Please try again.',
+ })
+ })
+ .finally(() => {
+ isSavingRole.value = false
+ })
+}