mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 09:34:50 +00:00
feat: move users page to xplat page system (#6889)
* feat: move users page to xplat * feat: stop doing tauri plugin open for user links * feat: qa * fix: qa * fix: qa * fix: comment * fix: prepr * prepr * prepr * prepr --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
:key="`member-${member.id}`"
|
||||
class="flex gap-2 items-center w-fit text-primary leading-[1.2] group"
|
||||
:to="userLink(member.user.username)"
|
||||
:target="linkTarget ?? null"
|
||||
:target="resolveLinkTarget(userLinkTarget)"
|
||||
>
|
||||
<Avatar :src="member.user.avatar_url" :alt="member.user.username" size="32px" circle />
|
||||
<div class="flex flex-col">
|
||||
@@ -36,7 +36,7 @@
|
||||
v-tooltip="formatMessage(messages.owner)"
|
||||
class="text-brand-orange"
|
||||
/>
|
||||
<ExternalIcon v-if="linkTarget === '_blank'" />
|
||||
<ExternalIcon v-if="resolveLinkTarget(userLinkTarget) === '_blank'" />
|
||||
</span>
|
||||
<span class="text-sm font-normal text-secondary">{{ member.role }}</span>
|
||||
</div>
|
||||
@@ -79,8 +79,13 @@ const props = defineProps<{
|
||||
orgLink: (slug: string) => string
|
||||
userLink: (username: string) => string
|
||||
linkTarget?: string
|
||||
userLinkTarget?: string | null
|
||||
}>()
|
||||
|
||||
function resolveLinkTarget(target: string | null | undefined): string | null {
|
||||
return target === undefined ? (props.linkTarget ?? null) : target
|
||||
}
|
||||
|
||||
// Members should be an array of all members, without the accepted ones, and with the user with the Owner role at the start
|
||||
// The rest of the members should be sorted by role, then by name
|
||||
const sortedMembers = computed(() => {
|
||||
|
||||
@@ -76,7 +76,7 @@ const backupCreator = computed(() => {
|
||||
|
||||
const creatorProfileLink = computed(() =>
|
||||
backupCreator.value && backupCreator.value.id !== 'support'
|
||||
? `https://modrinth.com/user/${encodeURIComponent(backupCreator.value.username)}`
|
||||
? `/user/${encodeURIComponent(backupCreator.value.username)}`
|
||||
: undefined,
|
||||
)
|
||||
|
||||
@@ -224,8 +224,6 @@ const creatorAvatarSrc = computed(() =>
|
||||
<template v-else-if="backupCreator">
|
||||
<AutoLink
|
||||
:to="creatorProfileLink"
|
||||
:target="creatorProfileLink ? '_blank' : undefined"
|
||||
:rel="creatorProfileLink ? 'noopener noreferrer' : undefined"
|
||||
class="group flex min-w-0 items-center gap-1.5"
|
||||
:class="creatorProfileLink ? 'text-secondary hover:underline' : 'text-primary'"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<PageHeader :title="user.username" :summary="summary">
|
||||
<template #leading>
|
||||
<Avatar
|
||||
:src="user.avatar_url"
|
||||
:alt="user.username"
|
||||
:size="isModrinthUser ? '64px' : '96px'"
|
||||
:tint-by="user.username"
|
||||
circle
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="isOfficialAccount || showAffiliateBadge" #badges>
|
||||
<PageHeaderBadgeItem
|
||||
v-if="isOfficialAccount"
|
||||
:icon="BadgeCheckIcon"
|
||||
:icon-props="{ fill: 'var(--color-brand-highlight)' }"
|
||||
:tooltip="formatMessage(messages.officialAccount)"
|
||||
class="border-brand-highlight bg-brand-highlight text-brand"
|
||||
>
|
||||
{{ formatMessage(messages.officialAccount) }}
|
||||
</PageHeaderBadgeItem>
|
||||
<PageHeaderBadgeItem
|
||||
v-if="showAffiliateBadge"
|
||||
:icon="AffiliateIcon"
|
||||
class="border-brand-highlight bg-brand-highlight text-brand"
|
||||
>
|
||||
{{ formatMessage(messages.affiliateLabel) }}
|
||||
</PageHeaderBadgeItem>
|
||||
</template>
|
||||
|
||||
<template v-if="$slots.summary" #summary>
|
||||
<slot name="summary" />
|
||||
</template>
|
||||
|
||||
<template v-if="!isModrinthUser" #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataNumberItem
|
||||
:icon="BoxIcon"
|
||||
:value="projectsCount"
|
||||
:label="formatMessage(messages.profileProjectCountLabel, { count: projectsCount })"
|
||||
/>
|
||||
<PageHeaderMetadataNumberItem
|
||||
:icon="DownloadIcon"
|
||||
:value="downloads"
|
||||
:label="formatMessage(messages.profileDownloadCountLabel, { count: downloads })"
|
||||
:tooltip="downloadsTooltip"
|
||||
/>
|
||||
<PageHeaderMetadataTimeItem
|
||||
:icon="CalendarIcon"
|
||||
:date="user.created"
|
||||
:label="formatMessage(messages.profileJoinedLabel)"
|
||||
:tooltip="joinedTooltip"
|
||||
/>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<PageHeaderActions>
|
||||
<ButtonStyled v-if="isSelf" size="large">
|
||||
<AutoLink :to="editProfileLink">
|
||||
<EditIcon />
|
||||
{{ formatMessage(commonMessages.editButton) }}
|
||||
</AutoLink>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular size="large" type="transparent">
|
||||
<TeleportOverflowMenu
|
||||
:options="moreActions"
|
||||
:tooltip="formatMessage(commonMessages.moreOptionsButton)"
|
||||
:aria-label="formatMessage(commonMessages.moreOptionsButton)"
|
||||
>
|
||||
<MoreVerticalIcon />
|
||||
</TeleportOverflowMenu>
|
||||
</ButtonStyled>
|
||||
</PageHeaderActions>
|
||||
</template>
|
||||
</PageHeader>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
AffiliateIcon,
|
||||
BadgeCheckIcon,
|
||||
BoxIcon,
|
||||
CalendarIcon,
|
||||
ChartIcon,
|
||||
ClipboardCopyIcon,
|
||||
CurrencyIcon,
|
||||
DownloadIcon,
|
||||
EditIcon,
|
||||
InfoIcon,
|
||||
MoreVerticalIcon,
|
||||
ReportIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import AutoLink from '#ui/components/base/AutoLink.vue'
|
||||
import Avatar from '#ui/components/base/Avatar.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
import PageHeader from '#ui/components/base/page-header/index.vue'
|
||||
import PageHeaderMetadata from '#ui/components/base/page-header/metadata/index.vue'
|
||||
import PageHeaderMetadataNumberItem from '#ui/components/base/page-header/metadata/page-header-metadata-number-item.vue'
|
||||
import PageHeaderMetadataTimeItem from '#ui/components/base/page-header/metadata/page-header-metadata-time-item.vue'
|
||||
import PageHeaderActions from '#ui/components/base/page-header/page-header-actions.vue'
|
||||
import PageHeaderBadgeItem from '#ui/components/base/page-header/page-header-badge-item.vue'
|
||||
import type { Item as TeleportOverflowMenuItem } from '#ui/components/base/TeleportOverflowMenu.vue'
|
||||
import TeleportOverflowMenu from '#ui/components/base/TeleportOverflowMenu.vue'
|
||||
import { defineMessages, useFormatDateTime, useFormatNumber, useVIntl } from '#ui/composables'
|
||||
import type { AuthUser } from '#ui/providers/auth'
|
||||
import { commonMessages } from '#ui/utils'
|
||||
|
||||
const messages = defineMessages({
|
||||
affiliateLabel: {
|
||||
id: 'profile.label.affiliate',
|
||||
defaultMessage: 'Affiliate',
|
||||
},
|
||||
analyticsButton: {
|
||||
id: 'profile.button.analytics',
|
||||
defaultMessage: 'View user analytics',
|
||||
},
|
||||
billingButton: {
|
||||
id: 'profile.button.billing',
|
||||
defaultMessage: 'Manage user billing',
|
||||
},
|
||||
editRoleButton: {
|
||||
id: 'profile.button.edit-role',
|
||||
defaultMessage: 'Edit role',
|
||||
},
|
||||
infoButton: {
|
||||
id: 'profile.button.info',
|
||||
defaultMessage: 'View user details',
|
||||
},
|
||||
officialAccount: {
|
||||
id: 'profile.official-account',
|
||||
defaultMessage: 'Official Modrinth account',
|
||||
},
|
||||
profileJoinedLabel: {
|
||||
id: 'profile.label.joined',
|
||||
defaultMessage: 'Joined',
|
||||
},
|
||||
profileProjectCountLabel: {
|
||||
id: 'profile.label.project-count',
|
||||
defaultMessage: '{count, plural, one {project} other {projects}}',
|
||||
},
|
||||
profileDownloadCountLabel: {
|
||||
id: 'profile.label.download-count',
|
||||
defaultMessage: '{count, plural, one {download} other {downloads}}',
|
||||
},
|
||||
profileManageProjectsButton: {
|
||||
id: 'profile.button.manage-projects',
|
||||
defaultMessage: 'Manage projects',
|
||||
},
|
||||
removeAffiliateButton: {
|
||||
id: 'profile.button.remove-affiliate',
|
||||
defaultMessage: 'Remove as affiliate',
|
||||
},
|
||||
setAffiliateButton: {
|
||||
id: 'profile.button.set-affiliate',
|
||||
defaultMessage: 'Set as affiliate',
|
||||
},
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
user: Labrinth.Users.v3.User
|
||||
summary?: string | null
|
||||
authUser?: AuthUser | null
|
||||
editProfileLink?: string | (() => void)
|
||||
isModrinthUser?: boolean
|
||||
isOfficialAccount?: boolean
|
||||
showAffiliateBadge?: boolean
|
||||
isAffiliate?: boolean
|
||||
isSelf?: boolean
|
||||
isAdmin?: boolean
|
||||
isStaff?: boolean
|
||||
showStaffActions?: boolean
|
||||
projectsCount?: number
|
||||
downloads?: number
|
||||
}>(),
|
||||
{
|
||||
summary: null,
|
||||
authUser: null,
|
||||
editProfileLink: '/settings/profile',
|
||||
isModrinthUser: false,
|
||||
isOfficialAccount: false,
|
||||
showAffiliateBadge: false,
|
||||
isAffiliate: false,
|
||||
isSelf: false,
|
||||
isAdmin: false,
|
||||
isStaff: false,
|
||||
showStaffActions: false,
|
||||
projectsCount: 0,
|
||||
downloads: 0,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
manageProjects: []
|
||||
report: []
|
||||
copyId: []
|
||||
copyPermalink: []
|
||||
openBilling: []
|
||||
toggleAffiliate: []
|
||||
openInfo: []
|
||||
openAnalytics: []
|
||||
editRole: []
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatNumber = useFormatNumber()
|
||||
const formatDateTime = useFormatDateTime({
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'long',
|
||||
})
|
||||
const downloadsTooltip = computed(() => formatNumber(props.downloads))
|
||||
const joinedTooltip = computed(() => formatDateTime(props.user.created))
|
||||
|
||||
const moreActions = computed<TeleportOverflowMenuItem[]>(() => [
|
||||
{
|
||||
id: 'manage-projects',
|
||||
label: formatMessage(messages.profileManageProjectsButton),
|
||||
icon: BoxIcon,
|
||||
action: () => emit('manageProjects'),
|
||||
shown: props.isSelf,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
shown: props.isSelf,
|
||||
},
|
||||
{
|
||||
id: 'report',
|
||||
label: formatMessage(commonMessages.reportButton),
|
||||
icon: ReportIcon,
|
||||
action: () => emit('report'),
|
||||
color: 'red',
|
||||
shown: props.authUser?.id !== props.user.id,
|
||||
},
|
||||
{
|
||||
id: 'copy-id',
|
||||
label: formatMessage(commonMessages.copyIdButton),
|
||||
icon: ClipboardCopyIcon,
|
||||
action: () => emit('copyId'),
|
||||
},
|
||||
{
|
||||
id: 'copy-permalink',
|
||||
label: formatMessage(commonMessages.copyPermalinkButton),
|
||||
icon: ClipboardCopyIcon,
|
||||
action: () => emit('copyPermalink'),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
shown: props.showStaffActions && (props.isAdmin || props.isStaff),
|
||||
},
|
||||
{
|
||||
id: 'open-billing',
|
||||
label: formatMessage(messages.billingButton),
|
||||
icon: CurrencyIcon,
|
||||
action: () => emit('openBilling'),
|
||||
shown: props.showStaffActions && props.isStaff,
|
||||
},
|
||||
{
|
||||
id: 'toggle-affiliate',
|
||||
label: props.isAffiliate
|
||||
? formatMessage(messages.removeAffiliateButton)
|
||||
: formatMessage(messages.setAffiliateButton),
|
||||
icon: AffiliateIcon,
|
||||
action: () => emit('toggleAffiliate'),
|
||||
shown: props.showStaffActions && props.isAdmin,
|
||||
remainOnClick: true,
|
||||
color: props.isAffiliate ? 'red' : 'orange',
|
||||
},
|
||||
{
|
||||
id: 'open-info',
|
||||
label: formatMessage(messages.infoButton),
|
||||
icon: InfoIcon,
|
||||
action: () => emit('openInfo'),
|
||||
shown: props.showStaffActions && props.isStaff,
|
||||
},
|
||||
{
|
||||
id: 'open-analytics',
|
||||
label: formatMessage(messages.analyticsButton),
|
||||
icon: ChartIcon,
|
||||
action: () => emit('openAnalytics'),
|
||||
shown: props.showStaffActions && props.isAdmin,
|
||||
},
|
||||
{
|
||||
id: 'edit-role',
|
||||
label: formatMessage(messages.editRoleButton),
|
||||
icon: EditIcon,
|
||||
action: () => emit('editRole'),
|
||||
shown: props.showStaffActions && props.isAdmin,
|
||||
},
|
||||
])
|
||||
</script>
|
||||
@@ -1 +1,2 @@
|
||||
export { default as UserBadges } from './UserBadges.vue'
|
||||
export { default as UserPageHeader } from './UserPageHeader.vue'
|
||||
|
||||
Reference in New Issue
Block a user