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