feat: new proj moderation page (#6044)

* feat: new proj moderation page

* make requested changes

* add boolean for showing delay message

* fix server icon + shortened code

* fix server icon

* refactor admonitions

* msg correction.

* correction + change spam-notice

* Separate status info from instruction details

* Tweak timing delay msg, thread activity warning, and refer to moderation with consistent terms.

* Whoops, actually updated msgs correctly now.

* prepr + margin

* split out strings, simplify code again

* fix: a few more moderation fixes (#6048)

* fix: move tooltip to button

* fix: lock status buttons after pressing

* fix: unlisted/withheld icon on legacy badge

* prepprrr

* fix banners, add some extra dev mode stuff

* fix thread id copy padding

* tweak: adjust some of the status change messages (#6041)

* update messages & bunch of other stuff

* rename toggle

* change hover to 2.5, fix error size

* private msg overlay

---------

Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com>
This commit is contained in:
Prospector
2026-05-12 22:23:18 -07:00
committed by GitHub
co-authored by coolbot100s
parent d87f93fdd5
commit 0ffdabb2a3
39 changed files with 1963 additions and 766 deletions
@@ -1,7 +1,11 @@
<script setup lang="ts">
import { defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
import { XCircleIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
const { formatMessage } = useVIntl()
const flags = useFeatureFlags()
const tempIgnored = ref(false)
const messages = defineMessages({
title: {
@@ -13,16 +17,34 @@ const messages = defineMessages({
defaultMessage:
"This deploy of Modrinth's frontend failed to generate state from the API. This may be due to an outage or an error in configuration. Rebuild when the API is available. Error codes: {errors}; Current API URL is: {url}",
},
ignoreErrors: {
id: 'layout.banner.build-fail.ignore',
defaultMessage: 'Ignore',
},
alwaysIgnore: {
id: 'layout.banner.build-fail.always-ignore',
defaultMessage: 'Always ignore',
},
})
defineProps<{
errors: any[] | undefined
apiUrl: string
}>()
function alwaysIgnoreBanner() {
flags.value.alwaysIgnoreErrorBanner = true
saveFeatureFlags()
}
</script>
<template>
<PagewideBanner v-if="errors?.length" variant="error">
<PagewideBanner
v-if="
flags.showAllBanners || (errors?.length && !tempIgnored && !flags.alwaysIgnoreErrorBanner)
"
variant="error"
>
<template #title>
<span>{{ formatMessage(messages.title) }}</span>
</template>
@@ -34,5 +56,19 @@ defineProps<{
})
}}
</template>
<template #actions_right>
<ButtonStyled color="red" type="transparent" hover-color-fill="background">
<button @click="alwaysIgnoreBanner">
<XCircleIcon />
{{ formatMessage(messages.alwaysIgnore) }}
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button @click="tempIgnored = true">
<XIcon />
{{ formatMessage(messages.ignoreErrors) }}
</button>
</ButtonStyled>
</template>
</PagewideBanner>
</template>
@@ -13,6 +13,7 @@ import {
const { formatMessage } = useVIntl()
const flags = useFeatureFlags()
const config = useRuntimeConfig()
const route = useRoute()
const messages = defineMessages({
title: {
@@ -21,7 +22,7 @@ const messages = defineMessages({
},
description: {
id: 'layout.banner.preview.description',
defaultMessage: `If you meant to access the official Modrinth website, visit <link>https://modrinth.com</link>. This preview deploy is used by Modrinth staff for testing purposes. It was built using <branch-link>{owner}/{branch}</branch-link> @ {commit}.`,
defaultMessage: `If you meant to access the official Modrinth website, visit {url}. This preview deploy is used by Modrinth staff for testing purposes. It was built using <branch-link>{owner}/{branch}</branch-link> @ {commit}.`,
},
})
@@ -29,10 +30,12 @@ function hidePreviewBanner() {
flags.value.hidePreviewBanner = true
saveFeatureFlags()
}
const url = computed(() => `https://modrinth.com${route.fullPath}`)
</script>
<template>
<PagewideBanner v-if="!flags.hidePreviewBanner" variant="info">
<PagewideBanner v-if="!flags.hidePreviewBanner || flags.showAllBanners" variant="info">
<template #title>
<span>{{ formatMessage(messages.title) }}</span>
</template>
@@ -45,9 +48,9 @@ function hidePreviewBanner() {
branch: config.public.branch,
}"
>
<template #link="{ children }">
<a href="https://modrinth.com" target="_blank" rel="noopener" class="text-link">
<component :is="() => normalizeChildren(children)" />
<template #url>
<a :href="url" target="_blank" rel="noopener" class="text-link">
{{ url }}
</a>
</template>
<template #branch-link="{ children }">
@@ -75,7 +78,7 @@ function hidePreviewBanner() {
</IntlFormatted>
</span>
</template>
<template #actions_right>
<template #actions_top_right>
<ButtonStyled type="transparent" circular>
<button :aria-label="formatMessage(commonMessages.closeButton)" @click="hidePreviewBanner">
<XIcon aria-hidden="true" />
@@ -47,7 +47,7 @@ function hideRussiaCensorshipBanner() {
<span class="text-xs font-medium">(Перевод на русский)</span>
</nuxt-link>
</ButtonStyled>
<ButtonStyled>
<ButtonStyled type="transparent" hover-color-fill="background">
<nuxt-link to="/news/article/standing-by-our-values">
<BookTextIcon /> Read our full statement
<span class="text-xs font-medium">(English)</span>
@@ -55,7 +55,7 @@ function hideRussiaCensorshipBanner() {
</ButtonStyled>
</div>
</template>
<template #actions_right>
<template #actions_top_right>
<ButtonStyled circular type="transparent">
<button
v-tooltip="formatMessage(commonMessages.closeButton)"
@@ -10,6 +10,7 @@ import {
const { formatMessage } = useVIntl()
const cosmetics = useCosmetics()
const flags = useFeatureFlags()
const messages = defineMessages({
title: {
@@ -29,14 +30,14 @@ function hideStagingBanner() {
</script>
<template>
<PagewideBanner v-if="!cosmetics.hideStagingBanner" variant="warning">
<PagewideBanner v-if="flags.showAllBanners || !cosmetics.hideStagingBanner" variant="warning">
<template #title>
<span>{{ formatMessage(messages.title) }}</span>
</template>
<template #description>
{{ formatMessage(messages.description) }}
</template>
<template #actions_right>
<template #actions_top_right>
<ButtonStyled type="transparent" circular>
<button :aria-label="formatMessage(commonMessages.closeButton)" @click="hideStagingBanner">
<XIcon aria-hidden="true" />
@@ -29,8 +29,8 @@ const messages = defineMessages({
<template #description>
<span>{{ formatMessage(messages.description) }}</span>
</template>
<template #actions>
<ButtonStyled>
<template #actions_right>
<ButtonStyled color="red">
<nuxt-link to="/settings/billing">
<SettingsIcon aria-hidden="true" />
{{ formatMessage(messages.action) }}
@@ -55,7 +55,7 @@ function openTaxForm(e: MouseEvent) {
formatMessage(messages.description, { threshold: formatMoney(taxThreshold) })
}}</span>
</template>
<template #actions>
<template #actions_right>
<ButtonStyled color="orange">
<button @click="openTaxForm"><FileTextIcon /> {{ formatMessage(messages.action) }}</button>
</ButtonStyled>
@@ -29,7 +29,7 @@ const messages = defineMessages({
<template #description>
<span>{{ formatMessage(messages.description) }}</span>
</template>
<template #actions>
<template #actions_right>
<div class="flex w-fit flex-row">
<ButtonStyled color="red">
<nuxt-link to="https://support.modrinth.com" target="_blank" rel="noopener">
@@ -96,14 +96,12 @@ async function handleResendEmailVerification() {
}}
</span>
</template>
<template #actions>
<ButtonStyled v-if="hasEmail">
<button @click="handleResendEmailVerification">
<template #actions_right>
<ButtonStyled color="orange">
<button v-if="hasEmail" @click="handleResendEmailVerification">
{{ formatMessage(verifyEmailBannerMessages.action) }}
</button>
</ButtonStyled>
<ButtonStyled v-else>
<nuxt-link to="/settings/account">
<nuxt-link v-else to="/settings/account">
<SettingsIcon aria-hidden="true" />
{{ formatMessage(addEmailBannerMessages.action) }}
</nuxt-link>
@@ -0,0 +1,41 @@
<script setup lang="ts">
import { PagewideBanner } from '@modrinth/ui'
const flags = useFeatureFlags()
const route = useRoute()
const url = computed(() => `https://modrinth.com${route.fullPath}`)
const bannerRoot = ref<HTMLElement | null>(null)
function onProdLinkClick(e: MouseEvent) {
e.preventDefault()
const el = bannerRoot.value
if (el) {
const { height } = el.getBoundingClientRect()
window.scrollBy({ top: Math.ceil(height), behavior: 'auto' })
}
window.open(url.value, '_blank', 'noopener,noreferrer')
}
</script>
<template>
<div v-if="flags.showViewProdRouteBanner || flags.showAllBanners" ref="bannerRoot">
<PagewideBanner variant="info" slim>
<template #description>
<span>
View route on production:
<a
:href="url"
target="_blank"
rel="noopener noreferrer"
class="text-link"
@click="onProdLinkClick"
>
{{ url }}
</a>
</span>
</template>
</PagewideBanner>
</div>
</template>