You've already forked AstralRinth
forked from didirus/AstralRinth
New organizations (#1488)
* [WIP] Transfer organizations to own branch * push progress * Setup organizations page * Add organizations grid to user profile * Remove debug * Add error handling for failed organization fetch * Refactor organization page and settings * Restructure to composition setup api * checklist completion * Apply suggestions from code review Co-authored-by: Emma Alexia <emma@modrinth.com> * Update pages/[type]/[id]/settings/index.vue Co-authored-by: Emma Alexia <emma@modrinth.com> * Update pages/[type]/[id]/settings/index.vue Co-authored-by: Emma Alexia <emma@modrinth.com> * Update pages/[type]/[id]/settings/index.vue Co-authored-by: Emma Alexia <emma@modrinth.com> * Update pages/[type]/[id]/settings/index.vue Co-authored-by: Emma Alexia <emma@modrinth.com> * Clean up org state management * Refactor useClientTry to simplify code * Remove unused code and update dependencies * Refactor bulkEditLinks event handler * Refactor organization management functions * Update heading from "Creators" to "Members" * Refactor team member invitation * Refactor member management functions * Implement validation on clientside for org names * Name sanitization for fun characters * Update onInviteTeamMember function parameters * Remove name * sidebar * random rendering issue * Conform to org removal * Org no projects conditional * Update organization links in dashboard * Update Cards to universal-cards * Refactor gallery upload permissions * Refactor to sidebar pattern * Update button classes in gallery and versions components * Finish (most) * almost finish * Finish orgs :D * Fix lint * orgs fixes * fix most things * project settings * convert grid to cards * clean up unused test class * Settings -> Manage * add org view to org management * Fix prop mounting issue * fix analytics grid layout overflow * fix multiselect breaking layout * Refactor chart selection logic in ChartDisplay.vue * Add transfer modal --------- Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Emma Alexia <emma@modrinth.com>
This commit is contained in:
@@ -54,7 +54,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!collapsed" class="grid-display width-16">
|
||||
<div v-for="nag in nags.filter((x) => x.condition)" :key="nag.id" class="grid-display__item">
|
||||
<div
|
||||
v-for="nag in nags.filter((x) => x.condition && !x.hide)"
|
||||
:key="nag.id"
|
||||
class="grid-display__item"
|
||||
>
|
||||
<span class="label">
|
||||
<RequiredIcon
|
||||
v-if="nag.status === 'required'"
|
||||
@@ -101,7 +105,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { formatProjectType } from '~/plugins/shorthands.js'
|
||||
|
||||
import ChevronRightIcon from '~/assets/images/utils/chevron-right.svg'
|
||||
import DropdownIcon from '~/assets/images/utils/dropdown.svg'
|
||||
import CheckIcon from '~/assets/images/utils/check.svg'
|
||||
@@ -110,309 +116,266 @@ import RequiredIcon from '~/assets/images/utils/asterisk.svg'
|
||||
import SuggestionIcon from '~/assets/images/utils/lightbulb.svg'
|
||||
import ModerationIcon from '~/assets/images/sidebar/admin.svg'
|
||||
import SendIcon from '~/assets/images/utils/send.svg'
|
||||
import { acceptTeamInvite, removeSelfFromTeam } from '~/helpers/teams.js'
|
||||
import { acceptTeamInvite, removeTeamMember } from '~/helpers/teams.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ChevronRightIcon,
|
||||
DropdownIcon,
|
||||
CheckIcon,
|
||||
CrossIcon,
|
||||
RequiredIcon,
|
||||
SuggestionIcon,
|
||||
ModerationIcon,
|
||||
SendIcon,
|
||||
const props = defineProps({
|
||||
project: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
project: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
versions: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
currentMember: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
allMembers: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
isSettings: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
routeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
auth: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
tags: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
setProcessing: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: 'An error occurred',
|
||||
text: 'setProcessing function not found',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
toggleCollapsed: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: 'An error occurred',
|
||||
text: 'toggleCollapsed function not found',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
updateMembers: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: 'An error occurred',
|
||||
text: 'updateMembers function not found',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
versions: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
featuredGalleryImage() {
|
||||
return this.project.gallery.find((img) => img.featured)
|
||||
currentMember: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
allMembers: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
isSettings: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
routeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
auth: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
tags: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
setProcessing: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {
|
||||
addNotification({
|
||||
group: 'main',
|
||||
title: 'An error occurred',
|
||||
text: 'setProcessing function not found',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
nags() {
|
||||
return [
|
||||
{
|
||||
condition:
|
||||
this.project.body === '' || this.project.body.startsWith('# Placeholder description'),
|
||||
title: 'Add a description',
|
||||
id: 'add-description',
|
||||
description:
|
||||
"A description that clearly describes the project's purpose and function is required.",
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: 'Visit description settings',
|
||||
hide: this.routeName === 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !this.project.icon_url,
|
||||
title: 'Add an icon',
|
||||
id: 'add-icon',
|
||||
description:
|
||||
'Your project should have a nice-looking icon to uniquely identify your project at a glance.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: 'Visit general settings',
|
||||
hide: this.routeName === 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !this.featuredGalleryImage,
|
||||
title: 'Feature a gallery image',
|
||||
id: 'feature-gallery-image',
|
||||
description: 'Featured gallery images may be the first impression of many users.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'gallery',
|
||||
title: 'Visit gallery page',
|
||||
hide: this.routeName === 'type-id-gallery',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: this.versions.length < 1,
|
||||
title: 'Upload a version',
|
||||
id: 'upload-version',
|
||||
description: 'At least one version is required for a project to be submitted for review.',
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'versions',
|
||||
title: 'Visit versions page',
|
||||
hide: this.routeName === 'type-id-versions',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: this.project.categories.length < 1,
|
||||
title: 'Select tags',
|
||||
id: 'select-tags',
|
||||
description: 'Select all tags that apply to your project.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: 'Visit tag settings',
|
||||
hide: this.routeName === 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !(
|
||||
this.project.issues_url ||
|
||||
this.project.source_url ||
|
||||
this.project.wiki_url ||
|
||||
this.project.discord_url ||
|
||||
this.project.donation_urls.length > 0
|
||||
),
|
||||
title: 'Add external links',
|
||||
id: 'add-links',
|
||||
description:
|
||||
'Add any relevant links targeted outside of Modrinth, such as sources, issues, or a Discord invite.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: 'Visit links settings',
|
||||
hide: this.routeName === 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
{
|
||||
hide:
|
||||
this.project.project_type === 'resourcepack' ||
|
||||
this.project.project_type === 'plugin' ||
|
||||
this.project.project_type === 'shader' ||
|
||||
this.project.project_type === 'datapack',
|
||||
condition:
|
||||
this.project.client_side === 'unknown' || this.project.server_side === 'unknown',
|
||||
title: 'Select supported environments',
|
||||
id: 'select-environments',
|
||||
description: `Select if the ${this.$formatProjectType(
|
||||
this.project.project_type
|
||||
).toLowerCase()} functions on the client-side and/or server-side.`,
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: 'Visit general settings',
|
||||
hide: this.routeName === 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: this.project.license.id === 'LicenseRef-Unknown',
|
||||
title: 'Select license',
|
||||
id: 'select-license',
|
||||
description: `Select the license your ${this.$formatProjectType(
|
||||
this.project.project_type
|
||||
).toLowerCase()} is distributed under.`,
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'settings/license',
|
||||
title: 'Visit license settings',
|
||||
hide: this.routeName === 'type-id-settings-license',
|
||||
},
|
||||
},
|
||||
{
|
||||
hide: this.project.status !== 'draft',
|
||||
condition: true,
|
||||
title: 'Submit for review',
|
||||
id: 'submit-for-review',
|
||||
description:
|
||||
'Your project is only viewable by members of the project. It must be reviewed by moderators in order to be published.',
|
||||
status: 'review',
|
||||
link: null,
|
||||
action: {
|
||||
onClick: this.submitForReview,
|
||||
title: 'Submit for review',
|
||||
disabled: () =>
|
||||
this.nags.filter((x) => x.condition && x.status === 'required').length > 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
hide: !this.tags.rejectedStatuses.includes(this.project.status),
|
||||
condition: true,
|
||||
title: 'Resubmit for review',
|
||||
id: 'resubmit-for-review',
|
||||
description: `Your project has been ${this.project.status} by
|
||||
},
|
||||
toggleCollapsed: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {
|
||||
addNotification({
|
||||
group: 'main',
|
||||
title: 'An error occurred',
|
||||
text: 'toggleCollapsed function not found',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
updateMembers: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {
|
||||
addNotification({
|
||||
group: 'main',
|
||||
title: 'An error occurred',
|
||||
text: 'updateMembers function not found',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const featuredGalleryImage = computed(() => props.project.gallery.find((img) => img.featured))
|
||||
|
||||
const nags = computed(() => [
|
||||
{
|
||||
condition: props.versions.length < 1,
|
||||
title: 'Upload a version',
|
||||
id: 'upload-version',
|
||||
description: 'At least one version is required for a project to be submitted for review.',
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'versions',
|
||||
title: 'Visit versions page',
|
||||
hide: props.routeName === 'type-id-versions',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition:
|
||||
props.project.body === '' || props.project.body.startsWith('# Placeholder description'),
|
||||
title: 'Add a description',
|
||||
id: 'add-description',
|
||||
description:
|
||||
"A description that clearly describes the project's purpose and function is required.",
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'settings/description',
|
||||
title: 'Visit description settings',
|
||||
hide: props.routeName === 'type-id-settings-description',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !props.project.icon_url,
|
||||
title: 'Add an icon',
|
||||
id: 'add-icon',
|
||||
description:
|
||||
'Your project should have a nice-looking icon to uniquely identify your project at a glance.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: 'Visit general settings',
|
||||
hide: props.routeName === 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: props.project.gallery.length === 0 || !featuredGalleryImage,
|
||||
title: 'Feature a gallery image',
|
||||
id: 'feature-gallery-image',
|
||||
description: 'Featured gallery images may be the first impression of many users.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'gallery',
|
||||
title: 'Visit gallery page',
|
||||
hide: props.routeName === 'type-id-gallery',
|
||||
},
|
||||
},
|
||||
{
|
||||
hide: props.project.versions.length === 0,
|
||||
condition: props.project.categories.length < 1,
|
||||
title: 'Select tags',
|
||||
id: 'select-tags',
|
||||
description: 'Select all tags that apply to your project.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'settings/tags',
|
||||
title: 'Visit tag settings',
|
||||
hide: props.routeName === 'type-id-settings-tags',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: !(
|
||||
props.project.issues_url ||
|
||||
props.project.source_url ||
|
||||
props.project.wiki_url ||
|
||||
props.project.discord_url ||
|
||||
props.project.donation_urls.length > 0
|
||||
),
|
||||
title: 'Add external links',
|
||||
id: 'add-links',
|
||||
description:
|
||||
'Add any relevant links targeted outside of Modrinth, such as sources, issues, or a Discord invite.',
|
||||
status: 'suggestion',
|
||||
link: {
|
||||
path: 'settings/links',
|
||||
title: 'Visit links settings',
|
||||
hide: props.routeName === 'type-id-settings-links',
|
||||
},
|
||||
},
|
||||
{
|
||||
hide:
|
||||
props.project.versions.length === 0 ||
|
||||
props.project.project_type === 'resourcepack' ||
|
||||
props.project.project_type === 'plugin' ||
|
||||
props.project.project_type === 'shader' ||
|
||||
props.project.project_type === 'datapack',
|
||||
condition:
|
||||
props.project.client_side === 'unknown' ||
|
||||
props.project.server_side === 'unknown' ||
|
||||
(props.project.client_side === 'unsupported' && props.project.server_side === 'unsupported'),
|
||||
title: 'Select supported environments',
|
||||
id: 'select-environments',
|
||||
description: `Select if the ${formatProjectType(
|
||||
props.project.project_type
|
||||
).toLowerCase()} functions on the client-side and/or server-side.`,
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'settings',
|
||||
title: 'Visit general settings',
|
||||
hide: props.routeName === 'type-id-settings',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: props.project.license.id === 'LicenseRef-Unknown',
|
||||
title: 'Select license',
|
||||
id: 'select-license',
|
||||
description: `Select the license your ${formatProjectType(
|
||||
props.project.project_type
|
||||
).toLowerCase()} is distributed under.`,
|
||||
status: 'required',
|
||||
link: {
|
||||
path: 'settings/license',
|
||||
title: 'Visit license settings',
|
||||
hide: props.routeName === 'type-id-settings-license',
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: props.project.status === 'draft',
|
||||
title: 'Submit for review',
|
||||
id: 'submit-for-review',
|
||||
description:
|
||||
'Your project is only viewable by members of the project. It must be reviewed by moderators in order to be published.',
|
||||
status: 'review',
|
||||
link: null,
|
||||
action: {
|
||||
onClick: submitForReview,
|
||||
title: 'Submit for review',
|
||||
disabled: () => nags.value.filter((x) => x.condition && x.status === 'required').length > 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
condition: props.tags.rejectedStatuses.includes(props.project.status),
|
||||
title: 'Resubmit for review',
|
||||
id: 'resubmit-for-review',
|
||||
description: `Your project has been ${props.project.status} by
|
||||
Modrinth's staff. In most cases, you can resubmit for review after
|
||||
addressing the staff's message.`,
|
||||
status: 'review',
|
||||
link: {
|
||||
path: 'moderation',
|
||||
title: 'Visit moderation page',
|
||||
hide: this.routeName === 'type-id-moderation',
|
||||
},
|
||||
},
|
||||
]
|
||||
.filter((x) => !x.hide)
|
||||
.sort((a, b) =>
|
||||
this.sortByTrue(
|
||||
!a.condition,
|
||||
!b.condition,
|
||||
this.sortByTrue(
|
||||
a.status === 'required',
|
||||
b.status === 'required',
|
||||
this.sortByFalse(a.status === 'review', b.status === 'review')
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
showInvitation() {
|
||||
if (this.allMembers && this.auth) {
|
||||
const member = this.allMembers.find((x) => x.user.id === this.auth.user.id)
|
||||
return member && !member.accepted
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
acceptInvite() {
|
||||
acceptTeamInvite(this.project.team)
|
||||
this.updateMembers()
|
||||
},
|
||||
declineInvite() {
|
||||
removeSelfFromTeam(this.project.team)
|
||||
this.updateMembers()
|
||||
},
|
||||
sortByTrue(a, b, ifEqual = 0) {
|
||||
if (a === b) {
|
||||
return ifEqual
|
||||
} else if (a) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
},
|
||||
sortByFalse(a, b, ifEqual = 0) {
|
||||
if (a === b) {
|
||||
return ifEqual
|
||||
} else if (b) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
},
|
||||
async submitForReview() {
|
||||
if (
|
||||
!this.acknowledgedMessage ||
|
||||
this.nags.filter((x) => x.condition && x.status === 'required').length === 0
|
||||
) {
|
||||
await this.setProcessing()
|
||||
}
|
||||
status: 'review',
|
||||
link: {
|
||||
path: 'moderation',
|
||||
title: 'Visit moderation page',
|
||||
hide: props.routeName === 'type-id-moderation',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const showInvitation = computed(() => {
|
||||
if (props.allMembers && props.auth) {
|
||||
const member = props.allMembers.find((x) => x.user.id === props.auth.user.id)
|
||||
return member && !member.accepted
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
const acceptInvite = () => {
|
||||
acceptTeamInvite(props.project.team)
|
||||
props.updateMembers()
|
||||
}
|
||||
|
||||
const declineInvite = () => {
|
||||
removeTeamMember(props.project.team, props.auth.user.id)
|
||||
props.updateMembers()
|
||||
}
|
||||
|
||||
const submitForReview = async () => {
|
||||
if (
|
||||
!props.acknowledgedMessage ||
|
||||
nags.value.filter((x) => x.condition && x.status === 'required').length === 0
|
||||
) {
|
||||
await props.setProcessing()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user