refactor: move org context to new DI setup
This commit is contained in:
parent
1602aa9556
commit
718a35737a
@ -1,18 +1,20 @@
|
|||||||
import { ModrinthServerError } from "@modrinth/utils";
|
|
||||||
import type { JWTAuth, ModuleError, ModuleName } from "@modrinth/utils";
|
import type { JWTAuth, ModuleError, ModuleName } from "@modrinth/utils";
|
||||||
|
import { ModrinthServerError } from "@modrinth/utils";
|
||||||
|
import { injectNotificationManager } from "@modrinth/ui";
|
||||||
import { useServersFetch } from "./servers-fetch.ts";
|
import { useServersFetch } from "./servers-fetch.ts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GeneralModule,
|
|
||||||
ContentModule,
|
|
||||||
BackupsModule,
|
BackupsModule,
|
||||||
|
ContentModule,
|
||||||
|
FSModule,
|
||||||
|
GeneralModule,
|
||||||
NetworkModule,
|
NetworkModule,
|
||||||
StartupModule,
|
StartupModule,
|
||||||
WSModule,
|
WSModule,
|
||||||
FSModule,
|
|
||||||
} from "./modules/index.ts";
|
} from "./modules/index.ts";
|
||||||
|
|
||||||
export function handleError(err: any) {
|
export function handleError(err: any) {
|
||||||
|
const { addNotification } = injectNotificationManager();
|
||||||
if (err instanceof ModrinthServerError && err.v1Error) {
|
if (err instanceof ModrinthServerError && err.v1Error) {
|
||||||
addNotification({
|
addNotification({
|
||||||
title: err.v1Error?.context ?? `An error occurred`,
|
title: err.v1Error?.context ?? `An error occurred`,
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</h2>
|
</h2>
|
||||||
<span>
|
<span>
|
||||||
{{ $formatNumber(acceptedMembers?.length || 0) }}
|
{{ formatNumber(acceptedMembers?.length || 0) }}
|
||||||
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -120,11 +120,11 @@
|
|||||||
{
|
{
|
||||||
id: 'manage-projects',
|
id: 'manage-projects',
|
||||||
action: () =>
|
action: () =>
|
||||||
navigateTo('/organization/' + organization.slug + '/settings/projects'),
|
router.push('/organization/' + organization?.slug + '/settings/projects'),
|
||||||
hoverOnly: true,
|
hoverFilledOnly: true,
|
||||||
shown: auth.user && currentMember,
|
shown: !!(auth.user && currentMember),
|
||||||
},
|
},
|
||||||
{ divider: true, shown: auth.user && currentMember },
|
{ divider: true, shown: !!(auth?.user && currentMember) },
|
||||||
{ id: 'copy-id', action: () => copyId() },
|
{ id: 'copy-id', action: () => copyId() },
|
||||||
{ id: 'copy-permalink', action: () => copyPermalink() },
|
{ id: 'copy-permalink', action: () => copyPermalink() },
|
||||||
]"
|
]"
|
||||||
@ -157,20 +157,20 @@
|
|||||||
<template v-for="member in acceptedMembers" :key="member.user.id">
|
<template v-for="member in acceptedMembers" :key="member.user.id">
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
class="details-list__item details-list__item--type-large"
|
class="details-list__item details-list__item--type-large"
|
||||||
:to="`/user/${member.user.username}`"
|
:to="`/user/${member?.user?.username}`"
|
||||||
>
|
>
|
||||||
<Avatar :src="member.user.avatar_url" circle />
|
<Avatar :src="member?.user.avatar_url" circle />
|
||||||
<div class="rows">
|
<div class="rows">
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
{{ member.user.username }}
|
{{ member?.user?.username }}
|
||||||
<CrownIcon
|
<CrownIcon
|
||||||
v-if="member.is_owner"
|
v-if="member?.is_owner"
|
||||||
v-tooltip="'Organization owner'"
|
v-tooltip="'Organization owner'"
|
||||||
class="text-brand-orange"
|
class="text-brand-orange"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span class="details-list__item__text--style-secondary">
|
<span class="details-list__item__text--style-secondary">
|
||||||
{{ member.role ? member.role : "Member" }}
|
{{ member?.role ? member.role : "Member" }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
@ -196,16 +196,21 @@
|
|||||||
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
||||||
<NavTabs :links="navLinks" />
|
<NavTabs :links="navLinks" />
|
||||||
</div>
|
</div>
|
||||||
<template v-if="projects?.length > 0">
|
<template v-if="projects && projects.length > 0">
|
||||||
<div class="project-list display-mode--list">
|
<div class="project-list display-mode--list">
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
v-for="project in (route.params.projectType !== undefined
|
v-for="project in (route.params.projectType !== undefined
|
||||||
? projects.filter((x) =>
|
? (projects ?? []).filter((x) =>
|
||||||
x.project_types.includes(
|
x.project_types.includes(
|
||||||
route.params.projectType.substr(0, route.params.projectType.length - 1),
|
typeof route.params.projectType === 'string'
|
||||||
|
? route.params.projectType.slice(0, route.params.projectType.length - 1)
|
||||||
|
: route.params.projectType[0]?.slice(
|
||||||
|
0,
|
||||||
|
route.params.projectType[0].length - 1,
|
||||||
|
) || '',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: projects
|
: (projects ?? [])
|
||||||
)
|
)
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => b.downloads - a.downloads)"
|
.sort((a, b) => b.downloads - a.downloads)"
|
||||||
@ -225,9 +230,10 @@
|
|||||||
:client-side="project.client_side"
|
:client-side="project.client_side"
|
||||||
:server-side="project.server_side"
|
:server-side="project.server_side"
|
||||||
:status="
|
:status="
|
||||||
auth.user && (auth.user.id === user.id || tags.staffRoles.includes(auth.user.role))
|
auth.user &&
|
||||||
? project.status
|
(auth.user.id! === (user as any).id || tags.staffRoles.includes(auth.user.role))
|
||||||
: null
|
? (project.status as ProjectStatus)
|
||||||
|
: undefined
|
||||||
"
|
"
|
||||||
:type="project.project_types[0] ?? 'project'"
|
:type="project.project_types[0] ?? 'project'"
|
||||||
:color="project.color"
|
:color="project.color"
|
||||||
@ -240,9 +246,9 @@
|
|||||||
<br />
|
<br />
|
||||||
<span class="preserve-lines text">
|
<span class="preserve-lines text">
|
||||||
This organization doesn't have any projects yet.
|
This organization doesn't have any projects yet.
|
||||||
<template v-if="isPermission(currentMember?.organization_permissions, 1 << 4)">
|
<template v-if="isPermission(currentMember?.permissions, 1 << 4)">
|
||||||
Would you like to
|
Would you like to
|
||||||
<a class="link" @click="$refs.modal_creation.show()">create one</a>?
|
<a class="link" @click="($refs as any).modal_creation?.show()">create one</a>?
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -251,50 +257,58 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
BoxIcon,
|
BoxIcon,
|
||||||
MoreVerticalIcon,
|
|
||||||
UsersIcon,
|
|
||||||
SettingsIcon,
|
|
||||||
ChartIcon,
|
ChartIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
XIcon,
|
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
OrganizationIcon,
|
|
||||||
DownloadIcon,
|
|
||||||
CrownIcon,
|
CrownIcon,
|
||||||
|
DownloadIcon,
|
||||||
|
MoreVerticalIcon,
|
||||||
|
OrganizationIcon,
|
||||||
|
SettingsIcon,
|
||||||
|
UsersIcon,
|
||||||
|
XIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
ButtonStyled,
|
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
ContentPageHeader,
|
ContentPageHeader,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
commonMessages,
|
|
||||||
} from "@modrinth/ui";
|
} from "@modrinth/ui";
|
||||||
|
import type { Organization, ProjectStatus, ProjectType, ProjectV3 } from "@modrinth/utils";
|
||||||
|
import { formatNumber } from "@modrinth/utils";
|
||||||
|
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
||||||
|
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
||||||
|
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||||
import NavStack from "~/components/ui/NavStack.vue";
|
import NavStack from "~/components/ui/NavStack.vue";
|
||||||
import NavStackItem from "~/components/ui/NavStackItem.vue";
|
import NavStackItem from "~/components/ui/NavStackItem.vue";
|
||||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
|
||||||
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
|
||||||
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
|
||||||
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
|
|
||||||
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
|
||||||
import NavTabs from "~/components/ui/NavTabs.vue";
|
import NavTabs from "~/components/ui/NavTabs.vue";
|
||||||
|
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
||||||
|
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
||||||
|
import {
|
||||||
|
OrganizationContext,
|
||||||
|
provideOrganizationContext,
|
||||||
|
} from "~/providers/organization-context.ts";
|
||||||
|
import { isPermission } from "~/utils/permissions.ts";
|
||||||
|
|
||||||
const vintl = useVIntl();
|
const vintl = useVIntl();
|
||||||
const { formatMessage } = vintl;
|
const { formatMessage } = vintl;
|
||||||
|
|
||||||
const formatCompactNumber = useCompactNumber(true);
|
const formatCompactNumber = useCompactNumber(true);
|
||||||
|
|
||||||
const auth = await useAuth();
|
const auth: { user: any } & any = await useAuth();
|
||||||
const user = await useUser();
|
const user = await useUser();
|
||||||
const cosmetics = useCosmetics();
|
const cosmetics = useCosmetics();
|
||||||
const route = useNativeRoute();
|
const route = useNativeRoute();
|
||||||
|
const router = useRouter();
|
||||||
const tags = useTags();
|
const tags = useTags();
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
let orgId = useRouteId();
|
const orgId = useRouteId();
|
||||||
|
|
||||||
// hacky way to show the edit button on the corner of the card.
|
// hacky way to show the edit button on the corner of the card.
|
||||||
const routeHasSettings = computed(() => route.path.includes("settings"));
|
const routeHasSettings = computed(() => route.path.includes("settings"));
|
||||||
@ -303,12 +317,13 @@ const [
|
|||||||
{ data: organization, refresh: refreshOrganization },
|
{ data: organization, refresh: refreshOrganization },
|
||||||
{ data: projects, refresh: refreshProjects },
|
{ data: projects, refresh: refreshProjects },
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
useAsyncData(`organization/${orgId}`, () =>
|
useAsyncData(
|
||||||
useBaseFetch(`organization/${orgId}`, { apiVersion: 3 }),
|
`organization/${orgId}`,
|
||||||
|
() => useBaseFetch(`organization/${orgId}`, { apiVersion: 3 }) as Promise<Organization>,
|
||||||
),
|
),
|
||||||
useAsyncData(
|
useAsyncData(
|
||||||
`organization/${orgId}/projects`,
|
`organization/${orgId}/projects`,
|
||||||
() => useBaseFetch(`organization/${orgId}/projects`, { apiVersion: 3 }),
|
() => useBaseFetch(`organization/${orgId}/projects`, { apiVersion: 3 }) as Promise<ProjectV3[]>,
|
||||||
{
|
{
|
||||||
transform: (projects) => {
|
transform: (projects) => {
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
@ -359,7 +374,7 @@ if (!organization.value) {
|
|||||||
|
|
||||||
// Filter accepted, sort by role, then by name and Owner role always goes first
|
// Filter accepted, sort by role, then by name and Owner role always goes first
|
||||||
const acceptedMembers = computed(() => {
|
const acceptedMembers = computed(() => {
|
||||||
const acceptedMembers = organization.value.members?.filter((x) => x.accepted);
|
const acceptedMembers = organization.value?.members?.filter((x) => x.accepted) ?? [];
|
||||||
const owner = acceptedMembers.find((x) => x.is_owner);
|
const owner = acceptedMembers.find((x) => x.is_owner);
|
||||||
const rest = acceptedMembers.filter((x) => !x.is_owner) || [];
|
const rest = acceptedMembers.filter((x) => !x.is_owner) || [];
|
||||||
|
|
||||||
@ -374,43 +389,14 @@ const acceptedMembers = computed(() => {
|
|||||||
return [owner, ...rest];
|
return [owner, ...rest];
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentMember = computed(() => {
|
|
||||||
if (auth.value.user && organization.value) {
|
|
||||||
const member = organization.value.members.find((x) => x.user.id === auth.value.user.id);
|
|
||||||
|
|
||||||
if (member) {
|
|
||||||
return member;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tags.value.staffRoles.includes(auth.value.user.role)) {
|
|
||||||
return {
|
|
||||||
user: auth.value.user,
|
|
||||||
role: auth.value.user.role,
|
|
||||||
permissions: auth.value.user.role === "admin" ? 1023 : 12,
|
|
||||||
accepted: true,
|
|
||||||
payouts_split: 0,
|
|
||||||
avatar_url: auth.value.user.avatar_url,
|
|
||||||
name: auth.value.user.username,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasPermission = computed(() => {
|
|
||||||
const EDIT_DETAILS = 1 << 2;
|
|
||||||
return currentMember.value && (currentMember.value.permissions & EDIT_DETAILS) === EDIT_DETAILS;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isInvited = computed(() => {
|
const isInvited = computed(() => {
|
||||||
return currentMember.value?.accepted === false;
|
return currentMember.value?.accepted === false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectTypes = computed(() => {
|
const projectTypes = computed(() => {
|
||||||
const obj = {};
|
const obj: Record<string, boolean> = {};
|
||||||
|
|
||||||
for (const project of projects.value) {
|
for (const project of projects.value ?? []) {
|
||||||
obj[project.project_types[0] ?? "project"] = true;
|
obj[project.project_types[0] ?? "project"] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,62 +407,27 @@ const projectTypes = computed(() => {
|
|||||||
const sumDownloads = computed(() => {
|
const sumDownloads = computed(() => {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
|
||||||
for (const project of projects.value) {
|
for (const project of projects.value ?? []) {
|
||||||
sum += project.downloads;
|
sum += project.downloads;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sum;
|
return sum;
|
||||||
});
|
});
|
||||||
|
|
||||||
const patchIcon = async (icon) => {
|
|
||||||
const ext = icon.name.split(".").pop();
|
|
||||||
await useBaseFetch(`organization/${organization.value.id}/icon`, {
|
|
||||||
method: "PATCH",
|
|
||||||
body: icon,
|
|
||||||
query: { ext },
|
|
||||||
apiVersion: 3,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteIcon = async () => {
|
|
||||||
await useBaseFetch(`organization/${organization.value.id}/icon`, {
|
|
||||||
method: "DELETE",
|
|
||||||
apiVersion: 3,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const patchOrganization = async (id, newData) => {
|
|
||||||
await useBaseFetch(`organization/${id}`, {
|
|
||||||
method: "PATCH",
|
|
||||||
body: newData,
|
|
||||||
apiVersion: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (newData.slug) {
|
|
||||||
orgId = newData.slug;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAcceptInvite = useClientTry(async () => {
|
const onAcceptInvite = useClientTry(async () => {
|
||||||
await acceptTeamInvite(organization.value.team_id);
|
await acceptTeamInvite(organization.value?.team_id);
|
||||||
await refreshOrganization();
|
await refreshOrganization();
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDeclineInvite = useClientTry(async () => {
|
const onDeclineInvite = useClientTry(async () => {
|
||||||
await removeTeamMember(organization.value.team_id, auth.value?.user.id);
|
await removeTeamMember(organization.value?.team_id, auth.value?.user?.id);
|
||||||
await refreshOrganization();
|
await refreshOrganization();
|
||||||
});
|
});
|
||||||
|
|
||||||
provide("organizationContext", {
|
const organizationContext = new OrganizationContext(organization, projects, auth, tags, refresh);
|
||||||
organization,
|
const { currentMember } = organizationContext;
|
||||||
projects,
|
|
||||||
refresh,
|
provideOrganizationContext(organizationContext);
|
||||||
currentMember,
|
|
||||||
hasPermission,
|
|
||||||
patchIcon,
|
|
||||||
deleteIcon,
|
|
||||||
patchOrganization,
|
|
||||||
});
|
|
||||||
|
|
||||||
const title = `${organization.value.name} - Organization`;
|
const title = `${organization.value.name} - Organization`;
|
||||||
const description = `${organization.value.description} - View the organization ${organization.value.name} on Modrinth`;
|
const description = `${organization.value.description} - View the organization ${organization.value.name} on Modrinth`;
|
||||||
@ -492,13 +443,13 @@ useSeoMeta({
|
|||||||
const navLinks = computed(() => [
|
const navLinks = computed(() => [
|
||||||
{
|
{
|
||||||
label: formatMessage(commonMessages.allProjectType),
|
label: formatMessage(commonMessages.allProjectType),
|
||||||
href: `/organization/${organization.value.slug}`,
|
href: `/organization/${organization.value?.slug}`,
|
||||||
},
|
},
|
||||||
...projectTypes.value
|
...projectTypes.value
|
||||||
.map((x) => {
|
.map((x) => {
|
||||||
return {
|
return {
|
||||||
label: formatMessage(getProjectTypeMessage(x, true)),
|
label: formatMessage(getProjectTypeMessage(x as ProjectType, true)),
|
||||||
href: `/organization/${organization.value.slug}/${x}s`,
|
href: `/organization/${organization.value?.slug}/${x}s`,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.slice()
|
.slice()
|
||||||
@ -506,12 +457,12 @@ const navLinks = computed(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
async function copyId() {
|
async function copyId() {
|
||||||
await navigator.clipboard.writeText(organization.value.id);
|
await navigator.clipboard.writeText(organization.value?.id ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyPermalink() {
|
async function copyPermalink() {
|
||||||
await navigator.clipboard.writeText(
|
await navigator.clipboard.writeText(
|
||||||
`${config.public.siteUrl}/organization/${organization.value.id}`,
|
`${config.public.siteUrl}/organization/${organization.value?.id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ChartDisplay from "~/components/ui/charts/ChartDisplay.vue";
|
import ChartDisplay from "~/components/ui/charts/ChartDisplay.vue";
|
||||||
|
import { injectOrganizationContext } from "~/providers/organization-context.ts";
|
||||||
|
|
||||||
const { projects } = inject("organizationContext");
|
const { projects } = injectOrganizationContext();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { SaveIcon, TrashIcon, UploadIcon } from "@modrinth/assets";
|
import { SaveIcon, TrashIcon, UploadIcon } from "@modrinth/assets";
|
||||||
import { Avatar, Button, ConfirmModal, FileInput, injectNotificationManager } from "@modrinth/ui";
|
import { Avatar, Button, ConfirmModal, FileInput, injectNotificationManager } from "@modrinth/ui";
|
||||||
|
import { injectOrganizationContext } from "~/providers/organization-context.ts";
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager();
|
const { addNotification } = injectNotificationManager();
|
||||||
const {
|
const {
|
||||||
@ -10,7 +11,7 @@ const {
|
|||||||
deleteIcon,
|
deleteIcon,
|
||||||
patchIcon,
|
patchIcon,
|
||||||
patchOrganization,
|
patchOrganization,
|
||||||
} = inject("organizationContext");
|
} = injectOrganizationContext();
|
||||||
|
|
||||||
const icon = ref(null);
|
const icon = ref(null);
|
||||||
const deletedIcon = ref(false);
|
const deletedIcon = ref(false);
|
||||||
|
|||||||
@ -230,10 +230,11 @@ import {
|
|||||||
import { Avatar, Badge, Button, Checkbox, injectNotificationManager } from "@modrinth/ui";
|
import { Avatar, Badge, Button, Checkbox, injectNotificationManager } from "@modrinth/ui";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { removeTeamMember } from "~/helpers/teams.js";
|
import { removeTeamMember } from "~/helpers/teams.js";
|
||||||
|
import { injectOrganizationContext } from "~/providers/organization-context.ts";
|
||||||
import { isPermission } from "~/utils/permissions.ts";
|
import { isPermission } from "~/utils/permissions.ts";
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager();
|
const { addNotification } = injectNotificationManager();
|
||||||
const { organization, refresh: refreshOrganization, currentMember } = inject("organizationContext");
|
const { organization, refresh: refreshOrganization, currentMember } = injectOrganizationContext();
|
||||||
|
|
||||||
const auth = await useAuth();
|
const auth = await useAuth();
|
||||||
|
|
||||||
|
|||||||
@ -324,11 +324,12 @@ import { formatProjectType } from "@modrinth/utils";
|
|||||||
import { Multiselect } from "vue-multiselect";
|
import { Multiselect } from "vue-multiselect";
|
||||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||||
import OrganizationProjectTransferModal from "~/components/ui/OrganizationProjectTransferModal.vue";
|
import OrganizationProjectTransferModal from "~/components/ui/OrganizationProjectTransferModal.vue";
|
||||||
|
import { injectOrganizationContext } from "~/providers/organization-context.ts";
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager();
|
const { addNotification } = injectNotificationManager();
|
||||||
const { formatMessage } = useVIntl();
|
const { formatMessage } = useVIntl();
|
||||||
|
|
||||||
const { organization, projects, refresh } = inject("organizationContext");
|
const { organization, projects, refresh } = injectOrganizationContext();
|
||||||
|
|
||||||
const auth = await useAuth();
|
const auth = await useAuth();
|
||||||
|
|
||||||
|
|||||||
108
apps/frontend/src/providers/organization-context.ts
Normal file
108
apps/frontend/src/providers/organization-context.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { createContext } from "@modrinth/ui";
|
||||||
|
import { type Organization, type OrganizationMember, type ProjectV3 } from "@modrinth/utils";
|
||||||
|
|
||||||
|
export class OrganizationContext {
|
||||||
|
public readonly organization: Ref<Organization | null>;
|
||||||
|
public readonly projects: Ref<ProjectV3[] | null>;
|
||||||
|
private readonly auth: Ref<any>;
|
||||||
|
private readonly tags: Ref<any>;
|
||||||
|
private readonly refreshFunction: () => Promise<void>;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
organization: Ref<Organization | null>,
|
||||||
|
projects: Ref<ProjectV3[] | null>,
|
||||||
|
auth: Ref<any>,
|
||||||
|
tags: Ref<any>,
|
||||||
|
refreshFunction: () => Promise<void>,
|
||||||
|
) {
|
||||||
|
this.organization = organization;
|
||||||
|
this.projects = projects;
|
||||||
|
this.auth = auth;
|
||||||
|
this.tags = tags;
|
||||||
|
this.refreshFunction = refreshFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public refresh = async () => {
|
||||||
|
if (this.organization.value === null) {
|
||||||
|
throw new Error("Organization is not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.refreshFunction();
|
||||||
|
};
|
||||||
|
|
||||||
|
public currentMember = computed<Partial<OrganizationMember> | null>(() => {
|
||||||
|
if (this.auth.value.user && this.organization.value) {
|
||||||
|
const member = this.organization.value.members.find(
|
||||||
|
(x) => x.user.id === this.auth.value.user.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (member) {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tags.value.staffRoles.includes(this.auth.value.user.role)) {
|
||||||
|
return {
|
||||||
|
user: this.auth.value.user,
|
||||||
|
role: this.auth.value.user.role,
|
||||||
|
permissions: this.auth.value.user.role === "admin" ? 1023 : 12,
|
||||||
|
accepted: true,
|
||||||
|
payouts_split: 0,
|
||||||
|
avatar_url: this.auth.value.user.avatar_url,
|
||||||
|
name: this.auth.value.user.username,
|
||||||
|
} as Partial<OrganizationMember>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
public hasPermission = computed(() => {
|
||||||
|
const EDIT_DETAILS = 1 << 2;
|
||||||
|
return (
|
||||||
|
this.currentMember.value &&
|
||||||
|
(this.currentMember.value.permissions & EDIT_DETAILS) === EDIT_DETAILS
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
public patchIcon = async (icon: { name: string }) => {
|
||||||
|
if (this.organization.value === null) {
|
||||||
|
throw new Error("Organization is not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = icon.name.split(".").pop();
|
||||||
|
await useBaseFetch(`organization/${this.organization.value.id}/icon`, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: icon,
|
||||||
|
query: { ext },
|
||||||
|
apiVersion: 3,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public deleteIcon = async () => {
|
||||||
|
if (this.organization.value === null) {
|
||||||
|
throw new Error("Organization is not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await useBaseFetch(`organization/${this.organization.value.id}/icon`, {
|
||||||
|
method: "DELETE",
|
||||||
|
apiVersion: 3,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public patchOrganization = async (newData: { slug: any }) => {
|
||||||
|
if (this.organization.value === null) {
|
||||||
|
throw new Error("Organization is not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await useBaseFetch(`organization/${this.organization.value.id}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: newData,
|
||||||
|
apiVersion: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.refreshFunction();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const [injectOrganizationContext, provideOrganizationContext] =
|
||||||
|
createContext<OrganizationContext>("[id].vue", "organizationContext");
|
||||||
@ -42,6 +42,76 @@ export interface GalleryImage {
|
|||||||
description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProjectV3 {
|
||||||
|
id: ModrinthId
|
||||||
|
slug?: string
|
||||||
|
project_types: string[]
|
||||||
|
games: string[]
|
||||||
|
team_id: ModrinthId
|
||||||
|
organization?: ModrinthId
|
||||||
|
name: string
|
||||||
|
summary: string
|
||||||
|
description: string
|
||||||
|
|
||||||
|
published: string
|
||||||
|
updated: string
|
||||||
|
approved?: string
|
||||||
|
queued?: string
|
||||||
|
|
||||||
|
status: ProjectStatus
|
||||||
|
requested_status?: ProjectStatus
|
||||||
|
|
||||||
|
/** @deprecated moved to threads system */
|
||||||
|
moderator_message?: {
|
||||||
|
message: string
|
||||||
|
body?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
license: {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
downloads: number
|
||||||
|
followers: number
|
||||||
|
|
||||||
|
categories: string[]
|
||||||
|
additional_categories: string[]
|
||||||
|
loaders: string[]
|
||||||
|
|
||||||
|
versions: ModrinthId[]
|
||||||
|
icon_url?: string
|
||||||
|
|
||||||
|
link_urls: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
platform: string
|
||||||
|
donation: boolean
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
gallery: {
|
||||||
|
url: string
|
||||||
|
raw_url: string
|
||||||
|
featured: boolean
|
||||||
|
name?: string
|
||||||
|
description?: string
|
||||||
|
created: string
|
||||||
|
ordering: number
|
||||||
|
}[]
|
||||||
|
|
||||||
|
color?: number
|
||||||
|
thread_id: ModrinthId
|
||||||
|
monetization_status: MonetizationStatus
|
||||||
|
side_types_migration_review_status: 'reviewed' | 'pending'
|
||||||
|
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SideTypesMigrationReviewStatus = 'reviewed' | 'pending'
|
||||||
|
|
||||||
export interface Project {
|
export interface Project {
|
||||||
id: ModrinthId
|
id: ModrinthId
|
||||||
project_type: ProjectType
|
project_type: ProjectType
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user