Files
AstralRinth/apps/frontend/src/components/ui/create/CreateLimitAlert.vue
Prospector 1bbb01bd42 devex: migrate to vue-i18n (#4966)
* sample languages refactor

* feat: consistency + dedupe impl of i18n

* fix: broken imports

* fix: intl formatted component

* fix: use relative imports

* fix: imports

* fix: comment out incomplete locales + fix imports

* feat: cleanup

* fix: ui imports

* fix: lint

* fix: admonition import

* make footer a component, fix language reactivity

* make copyright notice untranslatable

---------

Co-authored-by: Calum H. <contact@cal.engineer>
2025-12-27 21:37:37 +00:00

168 lines
4.6 KiB
Vue

<template>
<Admonition
v-if="shouldShowAlert"
:type="hasHitLimit ? 'critical' : 'warning'"
:header="
hasHitLimit
? capitalizeString(formatMessage(messages.limitReached, { type: typeName.singular }))
: formatMessage(messages.approachingLimit, { type: typeName.singular, current, max })
"
class="mb-4"
>
<div class="flex w-full flex-col gap-4">
<template v-if="hasHitLimit">
{{ formatMessage(messages.limitReachedDescription, { type: typeName.singular, max }) }}
<div class="w-min">
<ButtonStyled color="red">
<NuxtLink to="https://support.modrinth.com" target="_blank">
<MessageIcon />{{ formatMessage(messages.contactSupport) }}</NuxtLink
>
</ButtonStyled>
</div>
</template>
<template v-else>
{{
formatMessage(messages.approachingLimitDescription, {
type: typeName.singular,
max,
typePlural: typeName.plural,
})
}}
<div class="w-min">
<ButtonStyled color="orange">
<NuxtLink to="https://support.modrinth.com" target="_blank">
<MessageIcon />{{ formatMessage(messages.contactSupport) }}</NuxtLink
>
</ButtonStyled>
</div>
</template>
</div>
</Admonition>
</template>
<script setup lang="ts">
import { MessageIcon } from '@modrinth/assets'
import { Admonition, ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
import { capitalizeString } from '@modrinth/utils'
import { computed, watch } from 'vue'
const { formatMessage } = useVIntl()
const messages = defineMessages({
limitReached: {
id: 'create.limit-alert.limit-reached',
defaultMessage: '{type} limit reached',
},
approachingLimit: {
id: 'create.limit-alert.approaching-limit',
defaultMessage: 'Approaching {type} limit ({current}/{max})',
},
limitReachedDescription: {
id: 'create.limit-alert.limit-reached-description',
defaultMessage:
"You've reached your {type} limit of {max}. Please contact support to increase your limit.",
},
approachingLimitDescription: {
id: 'create.limit-alert.approaching-limit-description',
defaultMessage:
"You're about to hit the {type} limit, please contact support if you need more than {max} {typePlural}.",
},
contactSupport: {
id: 'create.limit-alert.contact-support',
defaultMessage: 'Contact support',
},
typeProject: {
id: 'create.limit-alert.type-project',
defaultMessage: 'project',
},
typeOrganization: {
id: 'create.limit-alert.type-organization',
defaultMessage: 'organization',
},
typeCollection: {
id: 'create.limit-alert.type-collection',
defaultMessage: 'collection',
},
typePluralProject: {
id: 'create.limit-alert.type-plural-project',
defaultMessage: 'projects',
},
typePluralOrganization: {
id: 'create.limit-alert.type-plural-organization',
defaultMessage: 'organizations',
},
typePluralCollection: {
id: 'create.limit-alert.type-plural-collection',
defaultMessage: 'collections',
},
})
interface UserLimits {
current: number
max: number
}
const props = defineProps<{
type: 'project' | 'org' | 'collection'
}>()
const model = defineModel<boolean>()
const apiEndpoint = computed(() => {
switch (props.type) {
case 'project':
return 'limits/projects'
case 'org':
return 'limits/organizations'
case 'collection':
return 'limits/collections'
default:
return 'limits/projects'
}
})
const { data: limits } = await useAsyncData<UserLimits | undefined>(
`limits-${props.type}`,
() => useBaseFetch(apiEndpoint.value, { apiVersion: 3 }) as Promise<UserLimits>,
)
const typeName = computed<{ singular: string; plural: string }>(() => {
switch (props.type) {
case 'project':
return {
singular: formatMessage(messages.typeProject),
plural: formatMessage(messages.typePluralProject),
}
case 'org':
return {
singular: formatMessage(messages.typeOrganization),
plural: formatMessage(messages.typePluralOrganization),
}
case 'collection':
return {
singular: formatMessage(messages.typeCollection),
plural: formatMessage(messages.typePluralCollection),
}
default:
return {
singular: formatMessage(messages.typeProject),
plural: formatMessage(messages.typePluralProject),
}
}
})
const current = computed(() => limits.value?.current ?? 0)
const max = computed(() => limits.value?.max ?? null)
const percentage = computed(() => (max.value ? Math.round((current.value / max.value) * 100) : 0))
const hasHitLimit = computed(() => max.value !== null && current.value >= max.value)
const shouldShowAlert = computed(() => max.value !== null && percentage.value >= 75)
watch(
hasHitLimit,
(newValue) => {
model.value = newValue
},
{ immediate: true },
)
</script>