diff --git a/apps/app-frontend/src/pages/collection/Index.vue b/apps/app-frontend/src/pages/collection/Index.vue new file mode 100644 index 000000000..c71a3a42d --- /dev/null +++ b/apps/app-frontend/src/pages/collection/Index.vue @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + {{ formatMessage(commonMessages.copyIdButton) }} + + + + + + + + + + + + + + + + + diff --git a/apps/app-frontend/src/pages/collection/index.js b/apps/app-frontend/src/pages/collection/index.js new file mode 100644 index 000000000..b06d36585 --- /dev/null +++ b/apps/app-frontend/src/pages/collection/index.js @@ -0,0 +1,3 @@ +import Index from './Index.vue' + +export { Index } diff --git a/apps/app-frontend/src/pages/organization/Index.vue b/apps/app-frontend/src/pages/organization/Index.vue index d3b2f819f..6842ee958 100644 --- a/apps/app-frontend/src/pages/organization/Index.vue +++ b/apps/app-frontend/src/pages/organization/Index.vue @@ -6,7 +6,7 @@ - + + @@ -54,12 +55,12 @@ import { commonMessages, OverflowMenu, UserHeader, - UserSidebarBadges + UserSidebarBadges, UserSidebarCollections } from '@modrinth/ui' import { ReportIcon, ClipboardCopyIcon, MoreVerticalIcon } from '@modrinth/assets' import { useVIntl } from '@vintl/vintl' import { useFetch } from '@/helpers/fetch' -import type { User, Project, Organization } from '@modrinth/utils' +import type { User, Project, Organization, Collection } from '@modrinth/utils' import { useBreadcrumbs } from '@/store/breadcrumbs' import { useTheming } from '@/store/theme' import { useInstanceContext } from '@/composables/instance-context' @@ -73,11 +74,15 @@ const { formatMessage } = useVIntl() const user: Ref = ref(null) const projects: Ref = ref([]) const organizations: Ref = ref([]) +const collections: Ref = ref([]) async function fetchUser() { - user.value = await useFetch(`https://api.modrinth.com/v2/user/${route.params.id}`).catch(handleError) - projects.value = await useFetch(`https://api.modrinth.com/v2/user/${route.params.id}/projects`).catch(handleError) - organizations.value = await useFetch(`https://api.modrinth.com/v3/user/${route.params.id}/organizations`).catch(handleError) + [ user.value, projects.value, organizations.value, collections.value ] = await Promise.all([ + useFetch(`https://api.modrinth.com/v2/user/${route.params.id}`).catch(handleError), + useFetch(`https://api.modrinth.com/v2/user/${route.params.id}/projects`).catch(handleError), + useFetch(`https://api.modrinth.com/v3/user/${route.params.id}/organizations`).catch(handleError), + useFetch(`https://api.modrinth.com/v3/user/${route.params.id}/collections`).catch(handleError) + ]) if (!user.value) { return; diff --git a/apps/app-frontend/src/routes.js b/apps/app-frontend/src/routes.js index e92363a9c..1f1652460 100644 --- a/apps/app-frontend/src/routes.js +++ b/apps/app-frontend/src/routes.js @@ -3,6 +3,7 @@ import * as Pages from '@/pages' import * as Project from '@/pages/project' import * as User from '@/pages/user' import * as Organization from '@/pages/organization' +import * as Collection from '@/pages/collection' import * as Instance from '@/pages/instance' import * as Library from '@/pages/library' @@ -122,6 +123,16 @@ export default new createRouter({ breadcrumb: [{ name: '?Organization', link: '/organization/{id}' }], }, }, + { + path: '/collection/:id', + name: 'Collection', + component: Collection.Index, + props: true, + meta: { + useContext: true, + breadcrumb: [{ name: '?Collection', link: '/collection/{id}' }], + }, + }, { path: '/instance/:id', name: 'Instance', diff --git a/packages/ui/src/components/base/AutoLink.vue b/packages/ui/src/components/base/AutoLink.vue index c3e132063..81a05df90 100644 --- a/packages/ui/src/components/base/AutoLink.vue +++ b/packages/ui/src/components/base/AutoLink.vue @@ -2,21 +2,29 @@ - + - + diff --git a/packages/ui/src/components/collection/CollectionHeader.vue b/packages/ui/src/components/collection/CollectionHeader.vue new file mode 100644 index 000000000..fa1400ac6 --- /dev/null +++ b/packages/ui/src/components/collection/CollectionHeader.vue @@ -0,0 +1,58 @@ + + + + + + + {{ collection.name }} + + + + + + + + {{ formatMessage(commonMessages.projectsStat, { count: formatNumber(collection.projects.length, false) }) }} + + + + + {{ formatMessage(commonMessages.updatedDate, { date: dayjs(collection.updated).fromNow() }) }} + + + + + + + + diff --git a/packages/ui/src/components/collection/CollectionSidebarCurator.vue b/packages/ui/src/components/collection/CollectionSidebarCurator.vue new file mode 100644 index 000000000..0417385f2 --- /dev/null +++ b/packages/ui/src/components/collection/CollectionSidebarCurator.vue @@ -0,0 +1,44 @@ + + + {{ formatMessage(messages.title) }} + + + + + + {{ user.username }} + + + + + + + + diff --git a/packages/ui/src/components/collection/CollectionSidebarDescription.vue b/packages/ui/src/components/collection/CollectionSidebarDescription.vue new file mode 100644 index 000000000..83de3d284 --- /dev/null +++ b/packages/ui/src/components/collection/CollectionSidebarDescription.vue @@ -0,0 +1,25 @@ + + + {{ formatMessage(messages.title) }} + + {{ collection.description }} + + + + diff --git a/packages/ui/src/components/collection/CollectionSidebarDetails.vue b/packages/ui/src/components/collection/CollectionSidebarDetails.vue new file mode 100644 index 000000000..9af746a5a --- /dev/null +++ b/packages/ui/src/components/collection/CollectionSidebarDetails.vue @@ -0,0 +1,39 @@ + + + {{ formatMessage(messages.title) }} + + + + + {{ formatMessage(commonMessages.createdAgoLabel, { ago: dayjs(collection.created).fromNow() }) }} + + + + + + diff --git a/packages/ui/src/components/collection/CollectionStatusBadge.vue b/packages/ui/src/components/collection/CollectionStatusBadge.vue new file mode 100644 index 000000000..586378514 --- /dev/null +++ b/packages/ui/src/components/collection/CollectionStatusBadge.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index b9bb86784..3622f4d5a 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -104,8 +104,16 @@ export { default as ThemeSelector } from './settings/ThemeSelector.vue' export { default as UserBadge } from './user/UserBadge.vue' export { default as UserHeader } from './user/UserHeader.vue' export { default as UserSidebarBadges } from './user/UserSidebarBadges.vue' +export { default as UserSidebarCollections } from './user/UserSidebarCollections.vue' export { default as UserSidebarOrganizations } from './user/UserSidebarOrganizations.vue' // Organization export { default as OrganizationHeader } from './organization/OrganizationHeader.vue' export { default as OrganizationSidebarMembers } from './organization/OrganizationSidebarMembers.vue' + +// Collection +export { default as CollectionHeader } from './collection/CollectionHeader.vue' +export { default as CollectionSidebarCurator } from './collection/CollectionSidebarCurator.vue' +export { default as CollectionSidebarDescription } from './collection/CollectionSidebarDescription.vue' +export { default as CollectionSidebarDetails } from './collection/CollectionSidebarDetails.vue' +export { default as CollectionStatusBadge } from './collection/CollectionStatusBadge.vue' diff --git a/packages/ui/src/components/project/ProjectStatusBadge.vue b/packages/ui/src/components/project/ProjectStatusBadge.vue index 4756e55e9..0eb008950 100644 --- a/packages/ui/src/components/project/ProjectStatusBadge.vue +++ b/packages/ui/src/components/project/ProjectStatusBadge.vue @@ -19,6 +19,7 @@ import type { Component } from 'vue' import { computed } from 'vue' import Badge from '../base/SimpleBadge.vue' import type { ProjectStatus } from '@modrinth/utils' +import { commonMessages } from '../../utils/common-messages' const props = defineProps<{ status: ProjectStatus @@ -34,17 +35,11 @@ const metadata = computed(() => ({ const statusMetadata: Record = { approved: { icon: GlobeIcon, - message: defineMessage({ - id: 'project.visibility.public', - defaultMessage: 'Public', - }), + message: commonMessages.publicLabel, }, unlisted: { icon: LinkIcon, - message: defineMessage({ - id: 'project.visibility.unlisted', - defaultMessage: 'Unlisted', - }), + message: commonMessages.unlistedLabel, }, withheld: { icon: LinkIcon, @@ -55,10 +50,7 @@ const statusMetadata: Record diff --git a/packages/ui/src/components/project/ProjectsList.vue b/packages/ui/src/components/project/ProjectsList.vue index c5dc8fa46..b5be4a098 100644 --- a/packages/ui/src/components/project/ProjectsList.vue +++ b/packages/ui/src/components/project/ProjectsList.vue @@ -1,8 +1,7 @@ - - - diff --git a/packages/ui/src/components/user/UserSidebarCollections.vue b/packages/ui/src/components/user/UserSidebarCollections.vue new file mode 100644 index 000000000..bff259d20 --- /dev/null +++ b/packages/ui/src/components/user/UserSidebarCollections.vue @@ -0,0 +1,62 @@ + + + {{ formatMessage(messages.title) }} + + + + + + + {{ collection.name }} + + + {{ formatMessage(commonMessages.projectsStat, { count: formatNumber(collection.projects.length, false) }) }} + + + + + + {{ collection.name }} + {{ collection.description }} + + + + + + + diff --git a/packages/ui/src/utils/common-messages.ts b/packages/ui/src/utils/common-messages.ts index 54746dc2b..bf5b931f0 100644 --- a/packages/ui/src/utils/common-messages.ts +++ b/packages/ui/src/utils/common-messages.ts @@ -110,7 +110,7 @@ export const commonMessages = defineMessages({ defaultMessage: '{count} {count, plural, one {member} other {members}}', }, privateLabel: { - id: 'collection.label.private', + id: 'label.private', defaultMessage: 'Private', }, publicLabel: { @@ -189,6 +189,10 @@ export const commonMessages = defineMessages({ id: 'label.unlisted', defaultMessage: 'Unlisted', }, + unknownLabel: { + id: 'label.unknown', + defaultMessage: 'Unknown', + }, uploadImageButton: { id: 'button.upload-image', defaultMessage: 'Upload image', diff --git a/packages/utils/types.ts b/packages/utils/types.ts index 6165372f4..df940aac6 100644 --- a/packages/utils/types.ts +++ b/packages/utils/types.ts @@ -335,3 +335,18 @@ export type OrganizationMember = { payouts_split: number ordering: number } + +export type Collection = { + id: ModrinthId + user: User + name: string + description: string + icon_url: string + color: number + status: CollectionStatus + created: string + updated: string + projects: ModrinthId[] +} + +export type CollectionStatus = 'listed' | 'unlisted' | 'private' | 'unknown'
{{ collection.name }}
{{ collection.description }}