feat(frontend): Make dashboard page localizable (#5727)
* Make dashboard page localizable * dashboard sidebar * prepr:frontend * don't change the keys * undo fix * fix any err * don't i18n csv * prepr:frontend * fix: do not use button key * prepr:frontend * capitalize string date --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -554,6 +554,9 @@
|
||||
"dashboard.affiliate-links.create.button": {
|
||||
"message": "Create affiliate link"
|
||||
},
|
||||
"dashboard.affiliate-links.empty.no-codes": {
|
||||
"message": "No affiliate codes found."
|
||||
},
|
||||
"dashboard.affiliate-links.error.title": {
|
||||
"message": "Error loading affiliate links"
|
||||
},
|
||||
@@ -572,6 +575,15 @@
|
||||
"dashboard.affiliate-links.search": {
|
||||
"message": "Search affiliate links..."
|
||||
},
|
||||
"dashboard.analytics.from-projects": {
|
||||
"message": "from {count} {count, plural, one {project} other {projects}}"
|
||||
},
|
||||
"dashboard.analytics.total-downloads": {
|
||||
"message": "Total downloads"
|
||||
},
|
||||
"dashboard.analytics.total-followers": {
|
||||
"message": "Total followers"
|
||||
},
|
||||
"dashboard.collections.button.create-new": {
|
||||
"message": "Create new"
|
||||
},
|
||||
@@ -596,6 +608,18 @@
|
||||
"dashboard.collections.long-title": {
|
||||
"message": "Your collections"
|
||||
},
|
||||
"dashboard.collections.placeholder.search": {
|
||||
"message": "Search collections..."
|
||||
},
|
||||
"dashboard.collections.sort.name-ascending": {
|
||||
"message": "Name (A-Z)"
|
||||
},
|
||||
"dashboard.collections.sort.recently-created": {
|
||||
"message": "Recently Created"
|
||||
},
|
||||
"dashboard.collections.sort.recently-updated": {
|
||||
"message": "Recently Updated"
|
||||
},
|
||||
"dashboard.creator-tax-form-modal.confirmation.download-button": {
|
||||
"message": "Download {formType}"
|
||||
},
|
||||
@@ -848,6 +872,168 @@
|
||||
"dashboard.creator-withdraw-modal.withdraw-limit-used": {
|
||||
"message": "You've used up your <b>{withdrawLimit}</b> withdrawal limit. You must complete a tax form to withdraw more."
|
||||
},
|
||||
"dashboard.head-title": {
|
||||
"message": "Dashboard"
|
||||
},
|
||||
"dashboard.notifications.button.mark-all-as-read": {
|
||||
"message": "Mark all as read"
|
||||
},
|
||||
"dashboard.notifications.button.view-history": {
|
||||
"message": "View history"
|
||||
},
|
||||
"dashboard.notifications.empty.no-unread": {
|
||||
"message": "You don't have any unread notifications."
|
||||
},
|
||||
"dashboard.notifications.error.loading": {
|
||||
"message": "Error loading notifications:"
|
||||
},
|
||||
"dashboard.notifications.history.label": {
|
||||
"message": "History"
|
||||
},
|
||||
"dashboard.notifications.history.title": {
|
||||
"message": "Notification history"
|
||||
},
|
||||
"dashboard.notifications.link.see-all": {
|
||||
"message": "See all"
|
||||
},
|
||||
"dashboard.notifications.link.view-history": {
|
||||
"message": "View notification history"
|
||||
},
|
||||
"dashboard.notifications.link.view-more": {
|
||||
"message": "View {extraNotifs} more {extraNotifs, plural, one {notification} other {notifications}}"
|
||||
},
|
||||
"dashboard.notifications.loading": {
|
||||
"message": "Loading notifications..."
|
||||
},
|
||||
"dashboard.organizations.button.create": {
|
||||
"message": "Create organization"
|
||||
},
|
||||
"dashboard.organizations.empty.cta": {
|
||||
"message": "Make an organization!"
|
||||
},
|
||||
"dashboard.organizations.error.fetch": {
|
||||
"message": "Failed to fetch organizations"
|
||||
},
|
||||
"dashboard.organizations.member-count": {
|
||||
"message": "{count} {count, plural, one {member} other {members}}"
|
||||
},
|
||||
"dashboard.organizations.title": {
|
||||
"message": "Organizations"
|
||||
},
|
||||
"dashboard.projects.bulk-edit-hint": {
|
||||
"message": "You can edit multiple projects at once by selecting them below."
|
||||
},
|
||||
"dashboard.projects.bulk-edit.server-disabled": {
|
||||
"message": "Server projects do not support bulk editing"
|
||||
},
|
||||
"dashboard.projects.empty": {
|
||||
"message": "You don't have any projects yet. Click the green button above to begin."
|
||||
},
|
||||
"dashboard.projects.head-title": {
|
||||
"message": "Projects"
|
||||
},
|
||||
"dashboard.projects.links.and-more": {
|
||||
"message": "and {count} more..."
|
||||
},
|
||||
"dashboard.projects.links.button.clear-link": {
|
||||
"message": "Clear link"
|
||||
},
|
||||
"dashboard.projects.links.button.edit": {
|
||||
"message": "Edit links"
|
||||
},
|
||||
"dashboard.projects.links.changes-applied": {
|
||||
"message": "Changes will be applied to <strong>{count}</strong> {count, plural, one {project} other {projects}}."
|
||||
},
|
||||
"dashboard.projects.links.description": {
|
||||
"message": "Any links you specify below will be overwritten on each of the selected projects. Any you leave blank will be ignored. You can clear a link from all selected projects using the trash can button."
|
||||
},
|
||||
"dashboard.projects.links.discord-invite.description": {
|
||||
"message": "An invitation link to your Discord server."
|
||||
},
|
||||
"dashboard.projects.links.discord-invite.label": {
|
||||
"message": "Discord invite"
|
||||
},
|
||||
"dashboard.projects.links.issue-tracker.description": {
|
||||
"message": "A place for users to report bugs, issues, and concerns about your project."
|
||||
},
|
||||
"dashboard.projects.links.issue-tracker.label": {
|
||||
"message": "Issue tracker"
|
||||
},
|
||||
"dashboard.projects.links.placeholder.cleared": {
|
||||
"message": "Existing link will be cleared"
|
||||
},
|
||||
"dashboard.projects.links.placeholder.valid-discord-url": {
|
||||
"message": "Enter a valid Discord invite URL"
|
||||
},
|
||||
"dashboard.projects.links.placeholder.valid-url": {
|
||||
"message": "Enter a valid URL"
|
||||
},
|
||||
"dashboard.projects.links.show-all-projects": {
|
||||
"message": "Show all projects"
|
||||
},
|
||||
"dashboard.projects.links.source-code.description": {
|
||||
"message": "A page/repository containing the source code for your project"
|
||||
},
|
||||
"dashboard.projects.links.source-code.label": {
|
||||
"message": "Source code"
|
||||
},
|
||||
"dashboard.projects.links.wiki-page.description": {
|
||||
"message": "A page containing information, documentation, and help for the project."
|
||||
},
|
||||
"dashboard.projects.links.wiki-page.label": {
|
||||
"message": "Wiki page"
|
||||
},
|
||||
"dashboard.projects.notification.bulk-edit-success": {
|
||||
"message": "Bulk edited selected project's links."
|
||||
},
|
||||
"dashboard.projects.project.icon-alt": {
|
||||
"message": "Icon for {title}"
|
||||
},
|
||||
"dashboard.projects.project.moderator-message-aria": {
|
||||
"message": "Project has a message from the moderators. View the project to see more."
|
||||
},
|
||||
"dashboard.projects.project.review-environment-metadata": {
|
||||
"message": "Please review environment metadata"
|
||||
},
|
||||
"dashboard.projects.sort.ascending": {
|
||||
"message": "Ascending"
|
||||
},
|
||||
"dashboard.projects.sort.descending": {
|
||||
"message": "Descending"
|
||||
},
|
||||
"dashboard.projects.sort.option.name": {
|
||||
"message": "Name"
|
||||
},
|
||||
"dashboard.projects.sort.option.status": {
|
||||
"message": "Status"
|
||||
},
|
||||
"dashboard.projects.sort.option.type": {
|
||||
"message": "Type"
|
||||
},
|
||||
"dashboard.projects.table.icon": {
|
||||
"message": "Icon"
|
||||
},
|
||||
"dashboard.projects.table.id": {
|
||||
"message": "ID"
|
||||
},
|
||||
"dashboard.projects.table.name": {
|
||||
"message": "Name"
|
||||
},
|
||||
"dashboard.projects.table.status": {
|
||||
"message": "Status"
|
||||
},
|
||||
"dashboard.projects.table.type": {
|
||||
"message": "Type"
|
||||
},
|
||||
"dashboard.report.title": {
|
||||
"message": "Report {id}"
|
||||
},
|
||||
"dashboard.reports.active-title": {
|
||||
"message": "Active reports"
|
||||
},
|
||||
"dashboard.reports.title": {
|
||||
"message": "Reports"
|
||||
},
|
||||
"dashboard.revenue.available-now": {
|
||||
"message": "Available now"
|
||||
},
|
||||
@@ -884,6 +1070,9 @@
|
||||
"dashboard.revenue.transactions.btn.download-csv": {
|
||||
"message": "Download as CSV"
|
||||
},
|
||||
"dashboard.revenue.transactions.head-title": {
|
||||
"message": "Transaction history"
|
||||
},
|
||||
"dashboard.revenue.transactions.header": {
|
||||
"message": "Transactions"
|
||||
},
|
||||
@@ -893,9 +1082,18 @@
|
||||
"dashboard.revenue.transactions.none.desc": {
|
||||
"message": "Your payouts and withdrawals will appear here."
|
||||
},
|
||||
"dashboard.revenue.transactions.period.last-month": {
|
||||
"message": "Last month"
|
||||
},
|
||||
"dashboard.revenue.transactions.period.this-month": {
|
||||
"message": "This month"
|
||||
},
|
||||
"dashboard.revenue.transactions.see-all": {
|
||||
"message": "See all"
|
||||
},
|
||||
"dashboard.revenue.transactions.year.all": {
|
||||
"message": "All years"
|
||||
},
|
||||
"dashboard.revenue.withdraw.blocked-tin-mismatch": {
|
||||
"message": "Your withdrawals are temporarily locked because your TIN or SSN didn't match IRS records. Please contact support to reset and resubmit your tax form."
|
||||
},
|
||||
@@ -908,6 +1106,33 @@
|
||||
"dashboard.revenue.withdraw.header": {
|
||||
"message": "Withdraw"
|
||||
},
|
||||
"dashboard.sidebar.label.activeReports": {
|
||||
"message": "Active reports"
|
||||
},
|
||||
"dashboard.sidebar.label.analytics": {
|
||||
"message": "Analytics"
|
||||
},
|
||||
"dashboard.sidebar.label.creators": {
|
||||
"message": "Creators"
|
||||
},
|
||||
"dashboard.sidebar.label.dashboard": {
|
||||
"message": "Dashboard"
|
||||
},
|
||||
"dashboard.sidebar.label.notifications": {
|
||||
"message": "Notifications"
|
||||
},
|
||||
"dashboard.sidebar.label.organizations": {
|
||||
"message": "Organizations"
|
||||
},
|
||||
"dashboard.sidebar.label.overview": {
|
||||
"message": "Overview"
|
||||
},
|
||||
"dashboard.sidebar.label.projects": {
|
||||
"message": "Projects"
|
||||
},
|
||||
"dashboard.sidebar.label.revenue": {
|
||||
"message": "Revenue"
|
||||
},
|
||||
"dashboard.withdraw.completion.account": {
|
||||
"message": "Account"
|
||||
},
|
||||
|
||||
@@ -3,26 +3,47 @@
|
||||
<div class="normal-page__sidebar">
|
||||
<NavStack
|
||||
:items="[
|
||||
{ type: 'heading', label: 'Dashboard' },
|
||||
{ link: '/dashboard', label: 'Overview', icon: DashboardIcon },
|
||||
{ link: '/dashboard/notifications', label: 'Notifications', icon: NotificationsIcon },
|
||||
{ link: '/dashboard/reports', label: 'Active reports', icon: ReportIcon },
|
||||
{ type: 'heading', label: formatMessage(messages.dashboard) },
|
||||
{ link: '/dashboard', label: formatMessage(messages.overview), icon: DashboardIcon },
|
||||
{
|
||||
link: '/dashboard/notifications',
|
||||
label: formatMessage(messages.notifications),
|
||||
icon: NotificationsIcon,
|
||||
},
|
||||
{
|
||||
link: '/dashboard/reports',
|
||||
label: formatMessage(messages.activeReports),
|
||||
icon: ReportIcon,
|
||||
},
|
||||
{
|
||||
link: '/dashboard/collections',
|
||||
label: formatMessage(commonMessages.collectionsLabel),
|
||||
icon: LibraryIcon,
|
||||
},
|
||||
{ type: 'heading', label: 'Creators' },
|
||||
{ link: '/dashboard/projects', label: 'Projects', icon: ListIcon },
|
||||
{ link: '/dashboard/organizations', label: 'Organizations', icon: OrganizationIcon },
|
||||
{ link: '/dashboard/analytics', label: 'Analytics', icon: ChartIcon },
|
||||
{ type: 'heading', label: formatMessage(messages.creators) },
|
||||
{ link: '/dashboard/projects', label: formatMessage(messages.projects), icon: ListIcon },
|
||||
{
|
||||
link: '/dashboard/organizations',
|
||||
label: formatMessage(messages.organizations),
|
||||
icon: OrganizationIcon,
|
||||
},
|
||||
{
|
||||
link: '/dashboard/analytics',
|
||||
label: formatMessage(messages.analytics),
|
||||
icon: ChartIcon,
|
||||
},
|
||||
{
|
||||
link: '/dashboard/affiliate-links',
|
||||
label: formatMessage(commonMessages.affiliateLinksButton),
|
||||
icon: AffiliateIcon,
|
||||
shown: !!isAffiliate,
|
||||
},
|
||||
{ link: '/dashboard/revenue', label: 'Revenue', icon: CurrencyIcon, matchNested: true },
|
||||
{
|
||||
link: '/dashboard/revenue',
|
||||
label: formatMessage(messages.revenue),
|
||||
icon: CurrencyIcon,
|
||||
matchNested: true,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
@@ -43,7 +64,7 @@ import {
|
||||
OrganizationIcon,
|
||||
ReportIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { commonMessages, useVIntl } from '@modrinth/ui'
|
||||
import { commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { type User, UserBadge } from '@modrinth/utils'
|
||||
|
||||
import NavStack from '~/components/ui/NavStack.vue'
|
||||
@@ -56,6 +77,45 @@ const isAffiliate = computed(() => {
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
dashboard: {
|
||||
id: 'dashboard.sidebar.label.dashboard',
|
||||
defaultMessage: 'Dashboard',
|
||||
},
|
||||
overview: {
|
||||
id: 'dashboard.sidebar.label.overview',
|
||||
defaultMessage: 'Overview',
|
||||
},
|
||||
notifications: {
|
||||
id: 'dashboard.sidebar.label.notifications',
|
||||
defaultMessage: 'Notifications',
|
||||
},
|
||||
activeReports: {
|
||||
id: 'dashboard.sidebar.label.activeReports',
|
||||
defaultMessage: 'Active reports',
|
||||
},
|
||||
creators: {
|
||||
id: 'dashboard.sidebar.label.creators',
|
||||
defaultMessage: 'Creators',
|
||||
},
|
||||
projects: {
|
||||
id: 'dashboard.sidebar.label.projects',
|
||||
defaultMessage: 'Projects',
|
||||
},
|
||||
organizations: {
|
||||
id: 'dashboard.sidebar.label.organizations',
|
||||
defaultMessage: 'Organizations',
|
||||
},
|
||||
analytics: {
|
||||
id: 'dashboard.sidebar.label.analytics',
|
||||
defaultMessage: 'Analytics',
|
||||
},
|
||||
revenue: {
|
||||
id: 'dashboard.sidebar.label.revenue',
|
||||
defaultMessage: 'Revenue',
|
||||
},
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
})
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
v-else-if="!filteredAffiliates || filteredAffiliates.length === 0"
|
||||
class="py-8 text-center"
|
||||
>
|
||||
<p class="text-secondary">No affiliate codes found.</p>
|
||||
<p class="text-secondary">{{ formatMessage(messages.noAffiliateCodesFound) }}</p>
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<AffiliateLinkCard
|
||||
@@ -166,6 +166,10 @@ const messages = defineMessages({
|
||||
id: 'dashboard.affiliate-links.error.title',
|
||||
defaultMessage: 'Error loading affiliate links',
|
||||
},
|
||||
noAffiliateCodesFound: {
|
||||
id: 'dashboard.affiliate-links.empty.no-codes',
|
||||
defaultMessage: 'No affiliate codes found.',
|
||||
},
|
||||
revokeConfirmButton: {
|
||||
id: 'dashboard.affiliate-links.revoke-confirm.button',
|
||||
defaultMessage: 'Revoke',
|
||||
|
||||
@@ -12,10 +12,17 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { injectModrinthClient, useDebugLogger } from '@modrinth/ui'
|
||||
import {
|
||||
commonProjectSettingsMessages,
|
||||
injectModrinthClient,
|
||||
useDebugLogger,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
|
||||
import ChartDisplay from '~/components/ui/charts/ChartDisplay.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const debug = useDebugLogger('analytics.vue')
|
||||
|
||||
definePageMeta({
|
||||
@@ -23,7 +30,7 @@ definePageMeta({
|
||||
})
|
||||
|
||||
useHead({
|
||||
title: 'Analytics - Modrinth',
|
||||
title: () => `${formatMessage(commonProjectSettingsMessages.analytics)} - Modrinth`,
|
||||
})
|
||||
|
||||
const auth = await useAuth()
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:icon="SearchIcon"
|
||||
type="text"
|
||||
clearable
|
||||
placeholder="Search collections..."
|
||||
:placeholder="formatMessage(messages.searchCollectionsPlaceholder)"
|
||||
wrapper-class="w-full"
|
||||
input-class="!h-12"
|
||||
/>
|
||||
@@ -20,18 +20,13 @@
|
||||
v-slot="{ selected }"
|
||||
v-model="sortBy"
|
||||
class="!w-auto flex-grow md:flex-grow-0"
|
||||
name="Sort by"
|
||||
:name="formatMessage(commonMessages.sortByLabel)"
|
||||
:options="['updated', 'created', 'name']"
|
||||
:display-name="
|
||||
(option) =>
|
||||
option === 'updated'
|
||||
? 'Recently Updated'
|
||||
: option === 'created'
|
||||
? 'Recently Created'
|
||||
: 'Name (A-Z)'
|
||||
"
|
||||
:display-name="formatCollectionSortOption"
|
||||
>
|
||||
<span class="font-semibold text-primary">Sort by: </span>
|
||||
<span class="font-semibold text-primary">{{
|
||||
formatMessage(commonMessages.sortByLabel)
|
||||
}}</span>
|
||||
<span class="font-semibold text-secondary">{{ selected }}</span>
|
||||
</DropdownSelect>
|
||||
|
||||
@@ -188,6 +183,22 @@ const messages = defineMessages({
|
||||
id: 'dashboard.collections.label.search-input',
|
||||
defaultMessage: 'Search your collections',
|
||||
},
|
||||
searchCollectionsPlaceholder: {
|
||||
id: 'dashboard.collections.placeholder.search',
|
||||
defaultMessage: 'Search collections...',
|
||||
},
|
||||
sortRecentlyUpdated: {
|
||||
id: 'dashboard.collections.sort.recently-updated',
|
||||
defaultMessage: 'Recently Updated',
|
||||
},
|
||||
sortRecentlyCreated: {
|
||||
id: 'dashboard.collections.sort.recently-created',
|
||||
defaultMessage: 'Recently Created',
|
||||
},
|
||||
sortNameAscending: {
|
||||
id: 'dashboard.collections.sort.name-ascending',
|
||||
defaultMessage: 'Name (A-Z)',
|
||||
},
|
||||
emptyNoMatch: {
|
||||
id: 'dashboard.collections.empty.no-match',
|
||||
defaultMessage: 'No collections match your search',
|
||||
@@ -234,6 +245,18 @@ const router = useNativeRouter()
|
||||
const validSortOptions = ['updated', 'created', 'name']
|
||||
const sortBy = ref(validSortOptions.includes(route.query.s) ? route.query.s : 'updated')
|
||||
|
||||
function formatCollectionSortOption(option) {
|
||||
if (option === 'updated') {
|
||||
return formatMessage(messages.sortRecentlyUpdated)
|
||||
}
|
||||
|
||||
if (option === 'created') {
|
||||
return formatMessage(messages.sortRecentlyCreated)
|
||||
}
|
||||
|
||||
return formatMessage(messages.sortNameAscending)
|
||||
}
|
||||
|
||||
const orderedCollections = computed(() => {
|
||||
if (!collections.value) return []
|
||||
return [...collections.value]
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{{ auth.user.username }}
|
||||
</h1>
|
||||
<NuxtLink class="goto-link" :to="`/user/${auth.user.username}`">
|
||||
Visit your profile
|
||||
{{ formatMessage(commonMessages.visitYourProfile) }}
|
||||
<ChevronRightIcon class="featured-header-chevron" aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
@@ -15,13 +15,15 @@
|
||||
<div class="dashboard-notifications">
|
||||
<section class="universal-card">
|
||||
<div class="header__row">
|
||||
<h2 class="header__title text-2xl">Notifications</h2>
|
||||
<h2 class="header__title text-2xl">
|
||||
{{ formatMessage(commonMessages.notificationsLabel) }}
|
||||
</h2>
|
||||
<nuxt-link
|
||||
v-if="notifications.length > 0"
|
||||
class="goto-link"
|
||||
to="/dashboard/notifications"
|
||||
>
|
||||
See all
|
||||
{{ formatMessage(messages.seeAll) }}
|
||||
<ChevronRightIcon />
|
||||
</nuxt-link>
|
||||
</div>
|
||||
@@ -42,15 +44,15 @@
|
||||
class="goto-link view-more-notifs mt-4"
|
||||
to="/dashboard/notifications"
|
||||
>
|
||||
View {{ extraNotifs }} more notification{{ extraNotifs === 1 ? '' : 's' }}
|
||||
{{ formatMessage(messages.viewMore, { extraNotifs: extraNotifs }) }}
|
||||
<ChevronRightIcon />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<div v-else class="universal-body">
|
||||
<p>You have no unread notifications.</p>
|
||||
<p>{{ formatMessage(messages.noUnreadNotifications) }}</p>
|
||||
<nuxt-link class="iconified-button !mt-4" to="/dashboard/notifications/history">
|
||||
<HistoryIcon />
|
||||
View notification history
|
||||
{{ formatMessage(messages.viewNotificationHistory) }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</section>
|
||||
@@ -58,18 +60,16 @@
|
||||
|
||||
<div class="dashboard-analytics">
|
||||
<section class="universal-card">
|
||||
<h2>Analytics</h2>
|
||||
<h2>{{ formatMessage(commonMessages.analyticsButton) }}</h2>
|
||||
<div class="grid-display">
|
||||
<div class="grid-display__item">
|
||||
<div class="label">Total downloads</div>
|
||||
<div class="label">{{ formatMessage(messages.totalDownloads) }}</div>
|
||||
<div class="value">
|
||||
{{ $formatNumber(projects.reduce((agg, x) => agg + x.downloads, 0)) }}
|
||||
</div>
|
||||
<span
|
||||
>from
|
||||
{{ downloadsProjectCount }}
|
||||
project{{ downloadsProjectCount === 1 ? '' : 's' }}</span
|
||||
>
|
||||
<span>{{
|
||||
formatMessage(messages.fromProjects, { count: downloadsProjectCount })
|
||||
}}</span>
|
||||
<!-- <NuxtLink class="goto-link" to="/dashboard/analytics"-->
|
||||
<!-- >View breakdown-->
|
||||
<!-- <ChevronRightIcon-->
|
||||
@@ -78,16 +78,15 @@
|
||||
<!-- /></NuxtLink>-->
|
||||
</div>
|
||||
<div class="grid-display__item">
|
||||
<div class="label">Total followers</div>
|
||||
<div class="label">{{ formatMessage(messages.totalFollowers) }}</div>
|
||||
<div class="value">
|
||||
{{ $formatNumber(projects.reduce((agg, x) => agg + x.followers, 0)) }}
|
||||
</div>
|
||||
<span>
|
||||
<span
|
||||
>from {{ followersProjectCount }} project{{
|
||||
followersProjectCount === 1 ? '' : 's'
|
||||
}}</span
|
||||
></span
|
||||
<span>{{
|
||||
formatMessage(messages.fromProjects, { count: followersProjectCount })
|
||||
}}</span></span
|
||||
>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,14 +96,58 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { ChevronRightIcon, HistoryIcon } from '@modrinth/assets'
|
||||
import { Avatar, injectModrinthClient } from '@modrinth/ui'
|
||||
import {
|
||||
Avatar,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
|
||||
import NotificationItem from '~/components/ui/NotificationItem.vue'
|
||||
import { fetchExtraNotificationData, groupNotifications } from '~/helpers/platform-notifications.ts'
|
||||
|
||||
useHead({
|
||||
title: 'Dashboard - Modrinth',
|
||||
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||
})
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
headTitle: {
|
||||
id: 'dashboard.head-title',
|
||||
defaultMessage: 'Dashboard',
|
||||
},
|
||||
seeAll: {
|
||||
id: 'dashboard.notifications.link.see-all',
|
||||
defaultMessage: 'See all',
|
||||
},
|
||||
viewMore: {
|
||||
id: 'dashboard.notifications.link.view-more',
|
||||
defaultMessage:
|
||||
'View {extraNotifs} more {extraNotifs, plural, one {notification} other {notifications}}',
|
||||
},
|
||||
noUnreadNotifications: {
|
||||
id: 'dashboard.notifications.empty.no-unread',
|
||||
defaultMessage: 'You have no unread notifications.',
|
||||
},
|
||||
viewNotificationHistory: {
|
||||
id: 'dashboard.notifications.link.view-history',
|
||||
defaultMessage: 'View notification history',
|
||||
},
|
||||
totalDownloads: {
|
||||
id: 'dashboard.analytics.total-downloads',
|
||||
defaultMessage: 'Total downloads',
|
||||
},
|
||||
totalFollowers: {
|
||||
id: 'dashboard.analytics.total-followers',
|
||||
defaultMessage: 'Total followers',
|
||||
},
|
||||
fromProjects: {
|
||||
id: 'dashboard.analytics.from-projects',
|
||||
defaultMessage: 'from {count} {count, plural, one {project} other {projects}}',
|
||||
},
|
||||
})
|
||||
|
||||
const auth = await useAuth()
|
||||
|
||||
@@ -3,22 +3,31 @@
|
||||
<section class="universal-card">
|
||||
<Breadcrumbs
|
||||
v-if="history"
|
||||
current-title="History"
|
||||
:link-stack="[{ href: `/dashboard/notifications`, label: 'Notifications' }]"
|
||||
:current-title="formatMessage(messages.historyLabel)"
|
||||
:link-stack="[
|
||||
{
|
||||
href: `/dashboard/notifications`,
|
||||
label: formatMessage(commonMessages.notificationsLabel),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<div class="header__row">
|
||||
<div class="header__title">
|
||||
<h2 v-if="history" class="text-2xl">Notification history</h2>
|
||||
<h2 v-else class="text-2xl">Notifications</h2>
|
||||
<h2 v-if="history" class="text-2xl">
|
||||
{{ formatMessage(messages.notificationHistoryTitle) }}
|
||||
</h2>
|
||||
<h2 v-else class="text-2xl">
|
||||
{{ formatMessage(commonMessages.notificationsLabel) }}
|
||||
</h2>
|
||||
</div>
|
||||
<template v-if="!history">
|
||||
<Button v-if="data.hasRead" @click="updateRoute()">
|
||||
<HistoryIcon />
|
||||
View history
|
||||
{{ formatMessage(messages.viewHistory) }}
|
||||
</Button>
|
||||
<Button v-if="notifications.length > 0" color="danger" @click="readAll()">
|
||||
<CheckCheckIcon />
|
||||
Mark all as read
|
||||
{{ formatMessage(messages.markAllAsRead) }}
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
@@ -29,9 +38,9 @@
|
||||
:format-label="(x) => (x === 'all' ? 'All' : formatProjectType(x).replace('_', ' ') + 's')"
|
||||
:capitalize="false"
|
||||
/>
|
||||
<p v-if="isPending">Loading notifications...</p>
|
||||
<p v-if="isPending">{{ formatMessage(messages.loadingNotifications) }}</p>
|
||||
<template v-else-if="error">
|
||||
<p>Error loading notifications:</p>
|
||||
<p>{{ formatMessage(messages.errorLoadingNotifications) }}</p>
|
||||
<pre>
|
||||
{{ error }}
|
||||
</pre>
|
||||
@@ -48,7 +57,7 @@
|
||||
@update:notifications="() => refetch()"
|
||||
/>
|
||||
</template>
|
||||
<p v-else>You don't have any unread notifications.</p>
|
||||
<p v-else>{{ formatMessage(messages.noUnreadNotifications) }}</p>
|
||||
<div class="flex justify-end">
|
||||
<Pagination :page="page" :count="pages" @switch-page="changePage" />
|
||||
</div>
|
||||
@@ -57,7 +66,15 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { CheckCheckIcon, HistoryIcon } from '@modrinth/assets'
|
||||
import { Button, Chips, injectModrinthClient, Pagination } from '@modrinth/ui'
|
||||
import {
|
||||
Button,
|
||||
Chips,
|
||||
commonMessages,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
Pagination,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatProjectType } from '@modrinth/utils'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
|
||||
@@ -69,8 +86,37 @@ import {
|
||||
markAsRead,
|
||||
} from '~/helpers/platform-notifications.ts'
|
||||
|
||||
useHead({
|
||||
title: 'Notifications - Modrinth',
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
historyLabel: {
|
||||
id: 'dashboard.notifications.history.label',
|
||||
defaultMessage: 'History',
|
||||
},
|
||||
notificationHistoryTitle: {
|
||||
id: 'dashboard.notifications.history.title',
|
||||
defaultMessage: 'Notification history',
|
||||
},
|
||||
viewHistory: {
|
||||
id: 'dashboard.notifications.button.view-history',
|
||||
defaultMessage: 'View history',
|
||||
},
|
||||
markAllAsRead: {
|
||||
id: 'dashboard.notifications.button.mark-all-as-read',
|
||||
defaultMessage: 'Mark all as read',
|
||||
},
|
||||
loadingNotifications: {
|
||||
id: 'dashboard.notifications.loading',
|
||||
defaultMessage: 'Loading notifications...',
|
||||
},
|
||||
errorLoadingNotifications: {
|
||||
id: 'dashboard.notifications.error.loading',
|
||||
defaultMessage: 'Error loading notifications:',
|
||||
},
|
||||
noUnreadNotifications: {
|
||||
id: 'dashboard.notifications.empty.no-unread',
|
||||
defaultMessage: "You don't have any unread notifications.",
|
||||
},
|
||||
})
|
||||
|
||||
const client = injectModrinthClient()
|
||||
@@ -79,6 +125,12 @@ const route = useNativeRoute()
|
||||
const router = useNativeRouter()
|
||||
|
||||
const history = computed(() => route.name === 'dashboard-notifications-history')
|
||||
|
||||
useHead({
|
||||
title: () =>
|
||||
`${formatMessage(history.value ? messages.notificationHistoryTitle : commonMessages.notificationsLabel)} - Modrinth`,
|
||||
})
|
||||
|
||||
const selectedType = ref('all')
|
||||
const page = ref(1)
|
||||
const perPage = ref(50)
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<OrganizationCreateModal ref="createOrgModal" />
|
||||
<section class="universal-card">
|
||||
<div class="header__row">
|
||||
<h2 class="header__title text-2xl">Organizations</h2>
|
||||
<h2 class="header__title text-2xl">{{ formatMessage(messages.organizationsTitle) }}</h2>
|
||||
<div class="input-group">
|
||||
<button class="iconified-button brand-button" @click="openCreateOrgModal">
|
||||
<PlusIcon aria-hidden="true" />
|
||||
Create organization
|
||||
{{ formatMessage(messages.createOrganization) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,10 +32,11 @@
|
||||
<div class="stats">
|
||||
<UsersIcon aria-hidden="true" />
|
||||
<span>
|
||||
{{ onlyAcceptedMembers(org.members).length }}
|
||||
member<template v-if="onlyAcceptedMembers(org.members).length !== 1"
|
||||
>s</template
|
||||
>
|
||||
{{
|
||||
formatMessage(messages.memberCount, {
|
||||
count: onlyAcceptedMembers(org.members).length,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
@@ -43,19 +44,44 @@
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else> Make an organization! </template>
|
||||
<template v-else> {{ formatMessage(messages.makeOrganization) }} </template>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { PlusIcon, UsersIcon } from '@modrinth/assets'
|
||||
import { Avatar, injectModrinthClient } from '@modrinth/ui'
|
||||
import { Avatar, defineMessages, injectModrinthClient, useVIntl } from '@modrinth/ui'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
|
||||
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
|
||||
import { useAuth } from '~/composables/auth.js'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
organizationsTitle: {
|
||||
id: 'dashboard.organizations.title',
|
||||
defaultMessage: 'Organizations',
|
||||
},
|
||||
createOrganization: {
|
||||
id: 'dashboard.organizations.button.create',
|
||||
defaultMessage: 'Create organization',
|
||||
},
|
||||
memberCount: {
|
||||
id: 'dashboard.organizations.member-count',
|
||||
defaultMessage: '{count} {count, plural, one {member} other {members}}',
|
||||
},
|
||||
makeOrganization: {
|
||||
id: 'dashboard.organizations.empty.cta',
|
||||
defaultMessage: 'Make an organization!',
|
||||
},
|
||||
fetchOrganizationsFailed: {
|
||||
id: 'dashboard.organizations.error.fetch',
|
||||
defaultMessage: 'Failed to fetch organizations',
|
||||
},
|
||||
})
|
||||
|
||||
const createOrgModal = ref(null)
|
||||
|
||||
const auth = await useAuth()
|
||||
@@ -77,7 +103,7 @@ const onlyAcceptedMembers = (members) => members.filter((member) => member?.acce
|
||||
if (error.value) {
|
||||
createError({
|
||||
statusCode: 500,
|
||||
message: 'Failed to fetch organizations',
|
||||
message: formatMessage(messages.fetchOrganizationsFailed),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<NewModal ref="editLinksModal" header="Edit links">
|
||||
<NewModal ref="editLinksModal" :header="formatMessage(messages.editLinksButton)">
|
||||
<div class="universal-modal links-modal !p-0">
|
||||
<p>
|
||||
Any links you specify below will be overwritten on each of the selected projects. Any you
|
||||
leave blank will be ignored. You can clear a link from all selected projects using the
|
||||
trash can button.
|
||||
</p>
|
||||
<p>{{ formatMessage(messages.editLinksDescription) }}</p>
|
||||
<section class="links">
|
||||
<label
|
||||
for="issue-tracker-input"
|
||||
title="A place for users to report bugs, issues, and concerns about your project."
|
||||
>
|
||||
<span class="label__title">Issue tracker</span>
|
||||
<label for="issue-tracker-input" :title="formatMessage(messages.issueTrackerDescription)">
|
||||
<span class="label__title">{{ formatMessage(messages.issueTrackerLabel) }}</span>
|
||||
</label>
|
||||
<div class="input-group shrink-first">
|
||||
<StyledInput
|
||||
@@ -20,14 +13,12 @@
|
||||
v-model="editLinks.issues.val"
|
||||
:disabled="editLinks.issues.clear"
|
||||
type="url"
|
||||
:placeholder="
|
||||
editLinks.issues.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
|
||||
"
|
||||
:placeholder="getLinkInputPlaceholder(editLinks.issues.clear)"
|
||||
:maxlength="2048"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
aria-label="Clear link"
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.issues.clear"
|
||||
@click="editLinks.issues.clear = !editLinks.issues.clear"
|
||||
@@ -35,11 +26,8 @@
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
<label
|
||||
for="source-code-input"
|
||||
title="A page/repository containing the source code for your project"
|
||||
>
|
||||
<span class="label__title">Source code</span>
|
||||
<label for="source-code-input" :title="formatMessage(messages.sourceCodeDescription)">
|
||||
<span class="label__title">{{ formatMessage(messages.sourceCodeLabel) }}</span>
|
||||
</label>
|
||||
<div class="input-group shrink-first">
|
||||
<StyledInput
|
||||
@@ -48,13 +36,11 @@
|
||||
:disabled="editLinks.source.clear"
|
||||
type="url"
|
||||
:maxlength="2048"
|
||||
:placeholder="
|
||||
editLinks.source.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
|
||||
"
|
||||
:placeholder="getLinkInputPlaceholder(editLinks.source.clear)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
aria-label="Clear link"
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.source.clear"
|
||||
@click="editLinks.source.clear = !editLinks.source.clear"
|
||||
@@ -62,11 +48,8 @@
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
<label
|
||||
for="wiki-page-input"
|
||||
title="A page containing information, documentation, and help for the project."
|
||||
>
|
||||
<span class="label__title">Wiki page</span>
|
||||
<label for="wiki-page-input" :title="formatMessage(messages.wikiPageDescription)">
|
||||
<span class="label__title">{{ formatMessage(messages.wikiPageLabel) }}</span>
|
||||
</label>
|
||||
<div class="input-group shrink-first">
|
||||
<StyledInput
|
||||
@@ -75,13 +58,11 @@
|
||||
:disabled="editLinks.wiki.clear"
|
||||
type="url"
|
||||
:maxlength="2048"
|
||||
:placeholder="
|
||||
editLinks.wiki.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
|
||||
"
|
||||
:placeholder="getLinkInputPlaceholder(editLinks.wiki.clear)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
aria-label="Clear link"
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.wiki.clear"
|
||||
@click="editLinks.wiki.clear = !editLinks.wiki.clear"
|
||||
@@ -89,8 +70,11 @@
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
<label for="discord-invite-input" title="An invitation link to your Discord server.">
|
||||
<span class="label__title">Discord invite</span>
|
||||
<label
|
||||
for="discord-invite-input"
|
||||
:title="formatMessage(messages.discordInviteDescription)"
|
||||
>
|
||||
<span class="label__title">{{ formatMessage(messages.discordInviteLabel) }}</span>
|
||||
</label>
|
||||
<div class="input-group shrink-first">
|
||||
<StyledInput
|
||||
@@ -99,15 +83,11 @@
|
||||
:disabled="editLinks.discord.clear"
|
||||
type="url"
|
||||
:maxlength="2048"
|
||||
:placeholder="
|
||||
editLinks.discord.clear
|
||||
? 'Existing link will be cleared'
|
||||
: 'Enter a valid Discord invite URL'
|
||||
"
|
||||
:placeholder="getLinkInputPlaceholder(editLinks.discord.clear, true)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="'Clear link'"
|
||||
aria-label="Clear link"
|
||||
v-tooltip="formatMessage(messages.clearLinkLabel)"
|
||||
:aria-label="formatMessage(messages.clearLinkLabel)"
|
||||
class="square-button label-button"
|
||||
:data-active="editLinks.discord.clear"
|
||||
@click="editLinks.discord.clear = !editLinks.discord.clear"
|
||||
@@ -117,10 +97,14 @@
|
||||
</div>
|
||||
</section>
|
||||
<p>
|
||||
Changes will be applied to
|
||||
<strong>{{ selectedProjects.length }}</strong> project{{
|
||||
selectedProjects.length > 1 ? 's' : ''
|
||||
}}.
|
||||
<IntlFormatted
|
||||
:message-id="messages.changesAppliedTo"
|
||||
:values="{ count: selectedProjects.length }"
|
||||
>
|
||||
<template #strong="{ children }">
|
||||
<strong><component :is="() => children" /></strong>
|
||||
</template>
|
||||
</IntlFormatted>
|
||||
</p>
|
||||
<ul>
|
||||
<li
|
||||
@@ -133,23 +117,25 @@
|
||||
{{ project.title }}
|
||||
</li>
|
||||
<li v-if="!editLinks.showAffected && selectedProjects.length > 3">
|
||||
<strong>and {{ selectedProjects.length - 3 }} more...</strong>
|
||||
<strong>{{
|
||||
formatMessage(messages.andMore, { count: selectedProjects.length - 3 })
|
||||
}}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<Checkbox
|
||||
v-if="selectedProjects.length > 3"
|
||||
v-model="editLinks.showAffected"
|
||||
label="Show all projects"
|
||||
description="Show all projects"
|
||||
:label="formatMessage(messages.showAllProjects)"
|
||||
:description="formatMessage(messages.showAllProjects)"
|
||||
/>
|
||||
<div class="push-right input-group">
|
||||
<button class="iconified-button" @click="$refs.editLinksModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
{{ formatMessage(commonMessages.cancelButton) }}
|
||||
</button>
|
||||
<button class="iconified-button brand-button" @click="bulkEditLinks()">
|
||||
<SaveIcon />
|
||||
Save changes
|
||||
{{ formatMessage(commonMessages.saveChangesButton) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,7 +143,7 @@
|
||||
<ModalCreation ref="modal_creation" />
|
||||
<section class="universal-card">
|
||||
<div class="header__row">
|
||||
<h2 class="header__title text-2xl">Projects</h2>
|
||||
<h2 class="header__title text-2xl">{{ formatMessage(messages.headTitle) }}</h2>
|
||||
<div class="input-group">
|
||||
<button class="iconified-button brand-button" @click="$refs.modal_creation.show($event)">
|
||||
<PlusIcon />
|
||||
@@ -166,10 +152,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="projects.length < 1">
|
||||
You don't have any projects yet. Click the green button above to begin.
|
||||
{{ formatMessage(messages.noProjectsYet) }}
|
||||
</p>
|
||||
<template v-else>
|
||||
<p>You can edit multiple projects at once by selecting them below.</p>
|
||||
<p>{{ formatMessage(messages.bulkEditHint) }}</p>
|
||||
<div class="input-group">
|
||||
<button
|
||||
class="iconified-button"
|
||||
@@ -177,11 +163,11 @@
|
||||
@click="$refs.editLinksModal.show()"
|
||||
>
|
||||
<EditIcon />
|
||||
Edit links
|
||||
{{ formatMessage(messages.editLinksButton) }}
|
||||
</button>
|
||||
<div class="push-right">
|
||||
<div class="labeled-control-row">
|
||||
Sort by
|
||||
{{ formatMessage(commonMessages.sortByLabel) }}
|
||||
<Combobox
|
||||
v-model="sortBy"
|
||||
:searchable="false"
|
||||
@@ -190,7 +176,7 @@
|
||||
@update:model-value="projects = updateSort(projects, sortBy, descending)"
|
||||
/>
|
||||
<button
|
||||
v-tooltip="descending ? 'Descending' : 'Ascending'"
|
||||
v-tooltip="formatMessage(descending ? messages.descending : messages.ascending)"
|
||||
class="square-button"
|
||||
@click="updateDescending()"
|
||||
>
|
||||
@@ -208,11 +194,11 @@
|
||||
@update:model-value="toggleAllBulkEditableProjects()"
|
||||
/>
|
||||
</div>
|
||||
<div>Icon</div>
|
||||
<div>Name</div>
|
||||
<div>ID</div>
|
||||
<div>Type</div>
|
||||
<div>Status</div>
|
||||
<div>{{ formatMessage(messages.iconHeader) }}</div>
|
||||
<div>{{ formatMessage(messages.nameHeader) }}</div>
|
||||
<div>{{ formatMessage(messages.idHeader) }}</div>
|
||||
<div>{{ formatMessage(messages.typeHeader) }}</div>
|
||||
<div>{{ formatMessage(messages.statusHeader) }}</div>
|
||||
<div />
|
||||
</div>
|
||||
<div v-for="project in projects" :key="`project-${project.id}`" class="grid-table__row">
|
||||
@@ -234,7 +220,7 @@
|
||||
<Avatar
|
||||
:src="project.icon_url"
|
||||
aria-hidden="true"
|
||||
:alt="'Icon for ' + project.title"
|
||||
:alt="formatMessage(messages.projectIconAlt, { title: project.title })"
|
||||
no-shadow
|
||||
/>
|
||||
</nuxt-link>
|
||||
@@ -244,7 +230,7 @@
|
||||
<span class="project-title">
|
||||
<IssuesIcon
|
||||
v-if="project.moderator_message"
|
||||
aria-label="Project has a message from the moderators. View the project to see more."
|
||||
:aria-label="formatMessage(messages.projectModeratorMessageAriaLabel)"
|
||||
/>
|
||||
|
||||
<nuxt-link
|
||||
@@ -277,7 +263,7 @@
|
||||
color="orange"
|
||||
>
|
||||
<nuxt-link
|
||||
v-tooltip="'Please review environment metadata'"
|
||||
v-tooltip="formatMessage(messages.reviewEnvironmentMetadata)"
|
||||
:to="`/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
|
||||
project.slug ? project.slug : project.id
|
||||
}?showEnvironmentMigrationWarning=true`"
|
||||
@@ -323,7 +309,9 @@ import {
|
||||
Combobox,
|
||||
commonMessages,
|
||||
CopyCode,
|
||||
defineMessages,
|
||||
injectNotificationManager,
|
||||
IntlFormatted,
|
||||
NewModal,
|
||||
ProjectStatusBadge,
|
||||
StyledInput,
|
||||
@@ -334,8 +322,6 @@ import { formatProjectType } from '@modrinth/utils'
|
||||
import ModalCreation from '~/components/ui/create/ProjectCreateModal.vue'
|
||||
import { getProjectTypeForUrl } from '~/helpers/projects.js'
|
||||
|
||||
useHead({ title: 'Projects - Modrinth' })
|
||||
|
||||
// const UPLOAD_VERSION = 1 << 0
|
||||
// const DELETE_VERSION = 1 << 1
|
||||
const EDIT_DETAILS = 1 << 2
|
||||
@@ -348,16 +334,163 @@ const EDIT_DETAILS = 1 << 2
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
headTitle: {
|
||||
id: 'dashboard.projects.head-title',
|
||||
defaultMessage: 'Projects',
|
||||
},
|
||||
editLinksButton: {
|
||||
id: 'dashboard.projects.links.button.edit',
|
||||
defaultMessage: 'Edit links',
|
||||
},
|
||||
editLinksDescription: {
|
||||
id: 'dashboard.projects.links.description',
|
||||
defaultMessage:
|
||||
'Any links you specify below will be overwritten on each of the selected projects. Any you leave blank will be ignored. You can clear a link from all selected projects using the trash can button.',
|
||||
},
|
||||
issueTrackerLabel: {
|
||||
id: 'dashboard.projects.links.issue-tracker.label',
|
||||
defaultMessage: 'Issue tracker',
|
||||
},
|
||||
issueTrackerDescription: {
|
||||
id: 'dashboard.projects.links.issue-tracker.description',
|
||||
defaultMessage: 'A place for users to report bugs, issues, and concerns about your project.',
|
||||
},
|
||||
sourceCodeLabel: {
|
||||
id: 'dashboard.projects.links.source-code.label',
|
||||
defaultMessage: 'Source code',
|
||||
},
|
||||
sourceCodeDescription: {
|
||||
id: 'dashboard.projects.links.source-code.description',
|
||||
defaultMessage: 'A page/repository containing the source code for your project',
|
||||
},
|
||||
wikiPageLabel: {
|
||||
id: 'dashboard.projects.links.wiki-page.label',
|
||||
defaultMessage: 'Wiki page',
|
||||
},
|
||||
wikiPageDescription: {
|
||||
id: 'dashboard.projects.links.wiki-page.description',
|
||||
defaultMessage: 'A page containing information, documentation, and help for the project.',
|
||||
},
|
||||
discordInviteLabel: {
|
||||
id: 'dashboard.projects.links.discord-invite.label',
|
||||
defaultMessage: 'Discord invite',
|
||||
},
|
||||
discordInviteDescription: {
|
||||
id: 'dashboard.projects.links.discord-invite.description',
|
||||
defaultMessage: 'An invitation link to your Discord server.',
|
||||
},
|
||||
existingLinkWillBeCleared: {
|
||||
id: 'dashboard.projects.links.placeholder.cleared',
|
||||
defaultMessage: 'Existing link will be cleared',
|
||||
},
|
||||
enterValidUrl: {
|
||||
id: 'dashboard.projects.links.placeholder.valid-url',
|
||||
defaultMessage: 'Enter a valid URL',
|
||||
},
|
||||
enterValidDiscordInviteUrl: {
|
||||
id: 'dashboard.projects.links.placeholder.valid-discord-url',
|
||||
defaultMessage: 'Enter a valid Discord invite URL',
|
||||
},
|
||||
clearLinkLabel: {
|
||||
id: 'dashboard.projects.links.button.clear-link',
|
||||
defaultMessage: 'Clear link',
|
||||
},
|
||||
changesAppliedTo: {
|
||||
id: 'dashboard.projects.links.changes-applied',
|
||||
defaultMessage:
|
||||
'Changes will be applied to <strong>{count}</strong> {count, plural, one {project} other {projects}}.',
|
||||
},
|
||||
andMore: {
|
||||
id: 'dashboard.projects.links.and-more',
|
||||
defaultMessage: 'and {count} more...',
|
||||
},
|
||||
showAllProjects: {
|
||||
id: 'dashboard.projects.links.show-all-projects',
|
||||
defaultMessage: 'Show all projects',
|
||||
},
|
||||
noProjectsYet: {
|
||||
id: 'dashboard.projects.empty',
|
||||
defaultMessage: "You don't have any projects yet. Click the green button above to begin.",
|
||||
},
|
||||
bulkEditHint: {
|
||||
id: 'dashboard.projects.bulk-edit-hint',
|
||||
defaultMessage: 'You can edit multiple projects at once by selecting them below.',
|
||||
},
|
||||
ascending: {
|
||||
id: 'dashboard.projects.sort.ascending',
|
||||
defaultMessage: 'Ascending',
|
||||
},
|
||||
descending: {
|
||||
id: 'dashboard.projects.sort.descending',
|
||||
defaultMessage: 'Descending',
|
||||
},
|
||||
sortOptionName: {
|
||||
id: 'dashboard.projects.sort.option.name',
|
||||
defaultMessage: 'Name',
|
||||
},
|
||||
sortOptionStatus: {
|
||||
id: 'dashboard.projects.sort.option.status',
|
||||
defaultMessage: 'Status',
|
||||
},
|
||||
sortOptionType: {
|
||||
id: 'dashboard.projects.sort.option.type',
|
||||
defaultMessage: 'Type',
|
||||
},
|
||||
iconHeader: {
|
||||
id: 'dashboard.projects.table.icon',
|
||||
defaultMessage: 'Icon',
|
||||
},
|
||||
nameHeader: {
|
||||
id: 'dashboard.projects.table.name',
|
||||
defaultMessage: 'Name',
|
||||
},
|
||||
idHeader: {
|
||||
id: 'dashboard.projects.table.id',
|
||||
defaultMessage: 'ID',
|
||||
},
|
||||
typeHeader: {
|
||||
id: 'dashboard.projects.table.type',
|
||||
defaultMessage: 'Type',
|
||||
},
|
||||
statusHeader: {
|
||||
id: 'dashboard.projects.table.status',
|
||||
defaultMessage: 'Status',
|
||||
},
|
||||
projectIconAlt: {
|
||||
id: 'dashboard.projects.project.icon-alt',
|
||||
defaultMessage: 'Icon for {title}',
|
||||
},
|
||||
projectModeratorMessageAriaLabel: {
|
||||
id: 'dashboard.projects.project.moderator-message-aria',
|
||||
defaultMessage: 'Project has a message from the moderators. View the project to see more.',
|
||||
},
|
||||
reviewEnvironmentMetadata: {
|
||||
id: 'dashboard.projects.project.review-environment-metadata',
|
||||
defaultMessage: 'Please review environment metadata',
|
||||
},
|
||||
serverBulkEditDisabled: {
|
||||
id: 'dashboard.projects.bulk-edit.server-disabled',
|
||||
defaultMessage: 'Server projects do not support bulk editing',
|
||||
},
|
||||
bulkEditSuccessText: {
|
||||
id: 'dashboard.projects.notification.bulk-edit-success',
|
||||
defaultMessage: "Bulk edited selected project's links.",
|
||||
},
|
||||
})
|
||||
|
||||
useHead({ title: () => `${formatMessage(messages.headTitle)} - Modrinth` })
|
||||
|
||||
const user = await useUser()
|
||||
const projects = ref([])
|
||||
const projectsWithMigrationWarning = ref([])
|
||||
const selectedProjects = ref([])
|
||||
const sortBy = ref('Name')
|
||||
const sortOptions = [
|
||||
{ value: 'Name', label: 'Name' },
|
||||
{ value: 'Status', label: 'Status' },
|
||||
{ value: 'Type', label: 'Type' },
|
||||
]
|
||||
const sortOptions = computed(() => [
|
||||
{ value: 'Name', label: formatMessage(messages.sortOptionName) },
|
||||
{ value: 'Status', label: formatMessage(messages.sortOptionStatus) },
|
||||
{ value: 'Type', label: formatMessage(messages.sortOptionType) },
|
||||
])
|
||||
const descending = ref(false)
|
||||
const editLinks = reactive({
|
||||
showAffected: false,
|
||||
@@ -370,6 +503,16 @@ const editLinks = reactive({
|
||||
const editLinksModal = ref(null)
|
||||
const modal_creation = ref(null)
|
||||
|
||||
function getLinkInputPlaceholder(clearLink, isDiscord = false) {
|
||||
if (clearLink) {
|
||||
return formatMessage(messages.existingLinkWillBeCleared)
|
||||
}
|
||||
|
||||
return isDiscord
|
||||
? formatMessage(messages.enterValidDiscordInviteUrl)
|
||||
: formatMessage(messages.enterValidUrl)
|
||||
}
|
||||
|
||||
function isProjectBulkEditDisabled(project) {
|
||||
return (
|
||||
(project.permissions & EDIT_DETAILS) === EDIT_DETAILS ||
|
||||
@@ -408,7 +551,7 @@ function toggleProjectSelection(project) {
|
||||
|
||||
function getBulkEditDisabledTooltip(project) {
|
||||
if (project.project_type === 'minecraft_java_server') {
|
||||
return 'Server projects do not support bulk editing'
|
||||
return formatMessage(messages.serverBulkEditDisabled)
|
||||
}
|
||||
|
||||
return ''
|
||||
@@ -467,8 +610,8 @@ async function bulkEditLinks() {
|
||||
|
||||
editLinksModal.value?.hide()
|
||||
addNotification({
|
||||
title: 'Success',
|
||||
text: "Bulk edited selected project's links.",
|
||||
title: formatMessage(commonMessages.successLabel),
|
||||
text: formatMessage(messages.bulkEditSuccessText),
|
||||
type: 'success',
|
||||
})
|
||||
selectedProjects.value = []
|
||||
@@ -483,7 +626,7 @@ async function bulkEditLinks() {
|
||||
editLinks.discord.clear = false
|
||||
} catch (e) {
|
||||
addNotification({
|
||||
title: 'An error occurred',
|
||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||
text: e,
|
||||
type: 'error',
|
||||
})
|
||||
|
||||
@@ -2,16 +2,33 @@
|
||||
<ReportView
|
||||
:auth="auth"
|
||||
:report-id="route.params.id"
|
||||
:breadcrumbs-stack="[{ href: '/dashboard/reports', label: 'Active reports' }]"
|
||||
:breadcrumbs-stack="[
|
||||
{ href: '/dashboard/reports', label: formatMessage(messages.activeReportsTitle) },
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
|
||||
import ReportView from '~/components/ui/report/ReportView.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
activeReportsTitle: {
|
||||
id: 'dashboard.reports.active-title',
|
||||
defaultMessage: 'Active reports',
|
||||
},
|
||||
reportTitle: {
|
||||
id: 'dashboard.report.title',
|
||||
defaultMessage: 'Report {id}',
|
||||
},
|
||||
})
|
||||
|
||||
const route = useNativeRoute()
|
||||
const auth = await useAuth()
|
||||
|
||||
useHead({
|
||||
title: `Report ${route.params.id} - Modrinth`,
|
||||
title: () => `${formatMessage(messages.reportTitle, { id: route.params.id })} - Modrinth`,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
<template>
|
||||
<div>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">Reports</h2>
|
||||
<h2 class="text-2xl">{{ formatMessage(messages.reportsTitle) }}</h2>
|
||||
<ReportsList :auth="auth" />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
|
||||
import ReportsList from '~/components/ui/report/ReportsList.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
reportsTitle: {
|
||||
id: 'dashboard.reports.title',
|
||||
defaultMessage: 'Reports',
|
||||
},
|
||||
activeReportsTitle: {
|
||||
id: 'dashboard.reports.active-title',
|
||||
defaultMessage: 'Active reports',
|
||||
},
|
||||
})
|
||||
|
||||
const auth = await useAuth()
|
||||
useHead({
|
||||
title: 'Active reports - Modrinth',
|
||||
title: () => `${formatMessage(messages.activeReportsTitle)} - Modrinth`,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -298,8 +298,14 @@ async function openWithdrawModal() {
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
balanceLabel: { id: 'dashboard.revenue.balance', defaultMessage: 'Balance' },
|
||||
availableNow: { id: 'dashboard.revenue.available-now', defaultMessage: 'Available now' },
|
||||
balanceLabel: {
|
||||
id: 'dashboard.revenue.balance',
|
||||
defaultMessage: 'Balance',
|
||||
},
|
||||
availableNow: {
|
||||
id: 'dashboard.revenue.available-now',
|
||||
defaultMessage: 'Available now',
|
||||
},
|
||||
estimatedWithDate: {
|
||||
id: 'dashboard.revenue.estimated-with-date',
|
||||
defaultMessage: 'Estimated {date}',
|
||||
@@ -312,14 +318,23 @@ const messages = defineMessages({
|
||||
id: 'dashboard.revenue.estimated-tooltip.msg2',
|
||||
defaultMessage: 'Click to read about how Modrinth handles your revenue.',
|
||||
},
|
||||
processing: { id: 'dashboard.revenue.processing', defaultMessage: 'Processing' },
|
||||
processing: {
|
||||
id: 'dashboard.revenue.processing',
|
||||
defaultMessage: 'Processing',
|
||||
},
|
||||
processingTooltip: {
|
||||
id: 'dashboard.revenue.processing.tooltip',
|
||||
defaultMessage:
|
||||
'Revenue stays in processing until the end of the month, then becomes available 60 days later.',
|
||||
},
|
||||
withdrawHeader: { id: 'dashboard.revenue.withdraw.header', defaultMessage: 'Withdraw' },
|
||||
withdrawCardTitle: { id: 'dashboard.revenue.withdraw.card.title', defaultMessage: 'Withdraw' },
|
||||
withdrawHeader: {
|
||||
id: 'dashboard.revenue.withdraw.header',
|
||||
defaultMessage: 'Withdraw',
|
||||
},
|
||||
withdrawCardTitle: {
|
||||
id: 'dashboard.revenue.withdraw.card.title',
|
||||
defaultMessage: 'Withdraw',
|
||||
},
|
||||
withdrawCardDescription: {
|
||||
id: 'dashboard.revenue.withdraw.card.description',
|
||||
defaultMessage: 'Withdraw from your available balance to any payout method.',
|
||||
@@ -338,7 +353,10 @@ const messages = defineMessages({
|
||||
id: 'dashboard.revenue.transactions.header',
|
||||
defaultMessage: 'Transactions',
|
||||
},
|
||||
seeAll: { id: 'dashboard.revenue.transactions.see-all', defaultMessage: 'See all' },
|
||||
seeAll: {
|
||||
id: 'dashboard.revenue.transactions.see-all',
|
||||
defaultMessage: 'See all',
|
||||
},
|
||||
noTransactions: {
|
||||
id: 'dashboard.revenue.transactions.none',
|
||||
defaultMessage: 'No transactions',
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
<Combobox
|
||||
v-model="selectedYear"
|
||||
:options="yearOptions"
|
||||
:display-value="selectedYear === 'all' ? 'All years' : String(selectedYear)"
|
||||
:display-value="
|
||||
selectedYear === 'all' ? formatMessage(messages.allYears) : String(selectedYear)
|
||||
"
|
||||
listbox
|
||||
/>
|
||||
<ButtonStyled circular>
|
||||
@@ -116,7 +118,7 @@ const client = injectModrinthClient()
|
||||
const generatedState = useGeneratedState()
|
||||
|
||||
useHead({
|
||||
title: 'Transaction history - Modrinth',
|
||||
title: () => `${formatMessage(messages.headTitle)} - Modrinth`,
|
||||
})
|
||||
|
||||
const { data: transactions, refetch } = useQuery({
|
||||
@@ -143,7 +145,7 @@ const yearOptions = computed(() => {
|
||||
|
||||
return yearValues.map((year) => ({
|
||||
value: year,
|
||||
label: year === 'all' ? 'All years' : String(year),
|
||||
label: year === 'all' ? formatMessage(messages.allYears) : String(year),
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -161,9 +163,9 @@ function getPeriodLabel(date) {
|
||||
const now = dayjs()
|
||||
|
||||
if (txnDate.isSame(now, 'month')) {
|
||||
return 'This month'
|
||||
return formatMessage(messages.thisMonth)
|
||||
} else if (txnDate.isSame(now.subtract(1, 'month'), 'month')) {
|
||||
return 'Last month'
|
||||
return formatMessage(messages.lastMonth)
|
||||
} else {
|
||||
return capitalizeString(formatMonth(txnDate.toDate()))
|
||||
}
|
||||
@@ -322,6 +324,10 @@ const messages = defineMessages({
|
||||
id: 'dashboard.revenue.transactions.header',
|
||||
defaultMessage: 'Transactions',
|
||||
},
|
||||
headTitle: {
|
||||
id: 'dashboard.revenue.transactions.head-title',
|
||||
defaultMessage: 'Transaction history',
|
||||
},
|
||||
received: {
|
||||
id: 'dashboard.revenue.stats.received',
|
||||
defaultMessage: 'Received',
|
||||
@@ -346,5 +352,17 @@ const messages = defineMessages({
|
||||
id: 'dashboard.revenue.transactions.btn.download-csv',
|
||||
defaultMessage: 'Download as CSV',
|
||||
},
|
||||
allYears: {
|
||||
id: 'dashboard.revenue.transactions.year.all',
|
||||
defaultMessage: 'All years',
|
||||
},
|
||||
thisMonth: {
|
||||
id: 'dashboard.revenue.transactions.period.this-month',
|
||||
defaultMessage: 'This month',
|
||||
},
|
||||
lastMonth: {
|
||||
id: 'dashboard.revenue.transactions.period.last-month',
|
||||
defaultMessage: 'Last month',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user