feat: introduce dependency injection framework (#4091)

* feat: migrate frontend notifications to dependency injection based notificaton manager

* fix: lint

* fix: issues

* fix: compile error + notif binding issue

* refactor: move org context to new DI setup

* feat: migrate app notifications to DI + frontend styling

* fix: sidebar issues

* fix: dont use delete in computed

* fix: import and prop issue

* refactor: move handleError to main notification manager class

* fix: lint & build

* fix: merge issues

* fix: lint issues

* fix: lint issues

---------

Signed-off-by: IMB11 <hendersoncal117@gmail.com>
Signed-off-by: Cal H. <hendersoncal117@gmail.com>
This commit is contained in:
Cal H.
2025-08-13 21:48:52 +01:00
committed by GitHub
parent 9ea43a12fd
commit b81e727204
136 changed files with 2024 additions and 1719 deletions

View File

@@ -1,11 +1,14 @@
<template>
<NuxtLayout>
<ModrinthLoadingIndicator />
<Notifications />
<NotificationPanel />
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
import { NotificationPanel, provideNotificationManager } from "@modrinth/ui";
import { FrontendNotificationManager } from "./providers/frontend-notifications.ts";
import ModrinthLoadingIndicator from "~/components/ui/modrinth-loading-indicator.ts";
import Notifications from "~/components/ui/Notifications.vue";
provideNotificationManager(new FrontendNotificationManager());
</script>

View File

@@ -51,7 +51,9 @@
<script setup>
import { PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const router = useNativeRouter();
const name = ref("");
@@ -87,7 +89,6 @@ async function create() {
await router.push(`/collection/${result.id}`);
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err,
type: "error",

View File

@@ -84,8 +84,11 @@
</template>
<script setup>
import { NewModal, ButtonStyled, DropdownSelect } from "@modrinth/ui";
import { XIcon, PlusIcon } from "@modrinth/assets";
import { PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, DropdownSelect, NewModal } from "@modrinth/ui";
import { injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const router = useRouter();
const app = useNuxtApp();
@@ -180,8 +183,7 @@ async function createProject() {
},
});
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -319,30 +319,21 @@
</template>
<script setup>
import { renderString } from "@modrinth/utils";
import {
UserPlusIcon,
ScaleIcon,
BellIcon,
CheckCircleIcon,
CalendarIcon,
VersionIcon,
CheckCircleIcon,
CheckIcon,
XIcon,
ExternalIcon,
ScaleIcon,
UserPlusIcon,
VersionIcon,
XIcon,
} from "@modrinth/assets";
import { Avatar, ProjectStatusBadge, CopyCode, useRelativeTime } from "@modrinth/ui";
import ThreadSummary from "~/components/ui/thread/ThreadSummary.vue";
import { getProjectLink, getVersionLink } from "~/helpers/projects.js";
import { getUserLink } from "~/helpers/users.js";
import { acceptTeamInvite, removeSelfFromTeam } from "~/helpers/teams.js";
import { markAsRead } from "~/helpers/notifications.ts";
import DoubleIcon from "~/components/ui/DoubleIcon.vue";
import Categories from "~/components/ui/search/Categories.vue";
import { injectNotificationManager } from "@modrinth/ui";
const app = useNuxtApp();
const { addNotification } = injectNotificationManager();
const emit = defineEmits(["update:notifications"]);
const formatRelativeTime = useRelativeTime();
const props = defineProps({
@@ -407,8 +398,7 @@ async function read() {
const newNotifs = updateNotifs(props.notifications);
emit("update:notifications", newNotifs);
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "Error marking notification as read",
text: err.data ? err.data.description : err,
type: "error",
@@ -427,8 +417,7 @@ async function performAction(notification, actionIndex) {
});
}
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -1,213 +0,0 @@
<template>
<div
class="vue-notification-group experimental-styles-within"
:class="{
'intercom-present': isIntercomPresent,
rightwards: moveNotificationsRight,
}"
>
<transition-group name="notifs">
<div
v-for="(item, index) in notifications"
:key="item.id"
class="vue-notification-wrapper"
@mouseenter="stopTimer(item)"
@mouseleave="setNotificationTimer(item)"
>
<div class="flex w-full gap-2 overflow-hidden rounded-lg bg-bg-raised shadow-xl">
<div
class="w-2"
:class="{
'bg-red': item.type === 'error',
'bg-orange': item.type === 'warning',
'bg-green': item.type === 'success',
'bg-blue': !item.type || !['error', 'warning', 'success'].includes(item.type),
}"
></div>
<div
class="grid w-full grid-cols-[auto_1fr_auto] items-center gap-x-2 gap-y-1 py-2 pl-1 pr-3"
>
<div
class="flex items-center"
:class="{
'text-red': item.type === 'error',
'text-orange': item.type === 'warning',
'text-green': item.type === 'success',
'text-blue': !item.type || !['error', 'warning', 'success'].includes(item.type),
}"
>
<IssuesIcon v-if="item.type === 'warning'" class="h-6 w-6" />
<CheckCircleIcon v-else-if="item.type === 'success'" class="h-6 w-6" />
<XCircleIcon v-else-if="item.type === 'error'" class="h-6 w-6" />
<InfoIcon v-else class="h-6 w-6" />
</div>
<div class="m-0 text-wrap font-bold text-contrast" v-html="item.title"></div>
<div class="flex items-center gap-1">
<div v-if="item.count && item.count > 1" class="text-xs font-bold text-contrast">
x{{ item.count }}
</div>
<ButtonStyled circular size="small">
<button v-tooltip="'Copy to clipboard'" @click="copyToClipboard(item)">
<CheckIcon v-if="copied[createNotifText(item)]" />
<CopyIcon v-else />
</button>
</ButtonStyled>
<ButtonStyled circular size="small">
<button v-tooltip="`Dismiss`" @click="notifications.splice(index, 1)">
<XIcon />
</button>
</ButtonStyled>
</div>
<div></div>
<div class="col-span-2 text-sm text-primary" v-html="item.text"></div>
<template v-if="item.errorCode">
<div></div>
<div
class="m-0 text-wrap text-xs font-medium text-secondary"
v-html="item.errorCode"
></div>
</template>
</div>
</div>
</div>
</transition-group>
</div>
</template>
<script setup>
import { ButtonStyled } from "@modrinth/ui";
import {
XCircleIcon,
CheckCircleIcon,
CheckIcon,
InfoIcon,
IssuesIcon,
XIcon,
CopyIcon,
} from "@modrinth/assets";
const notifications = useNotifications();
const { isVisible: moveNotificationsRight } = useNotificationRightwards();
const isIntercomPresent = ref(false);
function stopTimer(notif) {
clearTimeout(notif.timer);
}
const copied = ref({});
const createNotifText = (notif) => {
let text = "";
if (notif.title) {
text += notif.title;
}
if (notif.text) {
if (text.length > 0) {
text += "\n";
}
text += notif.text;
}
if (notif.errorCode) {
if (text.length > 0) {
text += "\n";
}
text += notif.errorCode;
}
return text;
};
function checkIntercomPresence() {
isIntercomPresent.value = !!document.querySelector(".intercom-lightweight-app");
}
onMounted(() => {
checkIntercomPresence();
const observer = new MutationObserver(() => {
checkIntercomPresence();
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
onBeforeUnmount(() => {
observer.disconnect();
});
});
function copyToClipboard(notif) {
const text = createNotifText(notif);
copied.value[text] = true;
navigator.clipboard.writeText(text);
setTimeout(() => {
delete copied.value[text];
}, 2000);
}
</script>
<style lang="scss" scoped>
.vue-notification-group {
position: fixed;
right: 1.5rem;
bottom: 1.5rem;
z-index: 200;
width: 450px;
@media screen and (max-width: 500px) {
width: calc(100% - 0.75rem * 2);
right: 0.75rem;
bottom: 0.75rem;
}
&.intercom-present {
bottom: 5rem;
}
&.rightwards {
right: unset !important;
left: 1.5rem;
@media screen and (max-width: 500px) {
left: 0.75rem;
}
}
.vue-notification-wrapper {
width: 100%;
overflow: hidden;
margin-bottom: 10px;
&:last-child {
margin: 0;
}
}
@media screen and (max-width: 750px) {
transition: bottom 0.25s ease-in-out;
bottom: calc(var(--size-mobile-navbar-height) + 10px) !important;
&.browse-menu-open {
bottom: calc(var(--size-mobile-navbar-height-expanded) + 10px) !important;
}
}
}
.notifs-enter-active,
.notifs-leave-active,
.notifs-move {
transition: all 0.25s ease-in-out;
}
.notifs-enter-from,
.notifs-leave-to {
opacity: 0;
}
.notifs-enter-from {
transform: translateY(100%) scale(0.8);
}
.notifs-leave-to {
transform: translateX(100%) scale(0.8);
}
</style>

View File

@@ -15,7 +15,7 @@
maxlength="64"
:placeholder="`Enter organization name...`"
autocomplete="off"
@input="updateSlug()"
@input="updateSlug"
/>
</div>
<div class="flex flex-col gap-2">
@@ -33,7 +33,7 @@
type="text"
maxlength="64"
autocomplete="off"
@input="manualSlug = true"
@input="setManualSlug"
/>
</div>
</div>
@@ -61,7 +61,7 @@
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="modal.hide()">
<button @click="hide">
<XIcon aria-hidden="true" />
Cancel
</button>
@@ -70,20 +70,22 @@
</div>
</NewModal>
</template>
<script setup>
import { XIcon, PlusIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
<script setup lang="ts">
import { PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal, injectNotificationManager } from "@modrinth/ui";
import { ref } from "vue";
const router = useNativeRouter();
const { addNotification } = injectNotificationManager();
const name = ref("");
const slug = ref("");
const description = ref("");
const manualSlug = ref(false);
const name = ref<string>("");
const slug = ref<string>("");
const description = ref<string>("");
const manualSlug = ref<boolean>(false);
const modal = ref<InstanceType<typeof NewModal>>();
const modal = ref();
async function createOrganization() {
async function createOrganization(): Promise<void> {
startLoading();
try {
const value = {
@@ -92,19 +94,18 @@ async function createOrganization() {
slug: slug.value.trim().replace(/ +/g, ""),
};
const result = await useBaseFetch("organization", {
const result: any = await useBaseFetch("organization", {
method: "POST",
body: JSON.stringify(value),
apiVersion: 3,
});
modal.value.hide();
modal.value?.hide();
await router.push(`/organization/${result.slug}`);
} catch (err) {
} catch (err: any) {
console.error(err);
addNotification({
group: "main",
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -112,13 +113,18 @@ async function createOrganization() {
}
stopLoading();
}
function show(event) {
function show(event?: MouseEvent): void {
name.value = "";
description.value = "";
modal.value.show(event);
modal.value?.show(event);
}
function updateSlug() {
function hide(): void {
modal.value?.hide();
}
function updateSlug(): void {
if (!manualSlug.value) {
slug.value = name.value
.trim()
@@ -129,6 +135,10 @@ function updateSlug() {
}
}
function setManualSlug(): void {
manualSlug.value = true;
}
defineExpose({
show,
});

View File

@@ -105,24 +105,25 @@
<script setup lang="ts">
import {
ChevronRightIcon,
CheckIcon,
XIcon,
AsteriskIcon,
LightBulbIcon,
TriangleAlertIcon,
CheckIcon,
ChevronRightIcon,
DropdownIcon,
SendIcon,
LightBulbIcon,
ScaleIcon,
InfoIcon,
SendIcon,
TriangleAlertIcon,
XIcon,
} from "@modrinth/assets";
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
import { nags } from "@modrinth/moderation";
import { ButtonStyled } from "@modrinth/ui";
import { useVIntl, defineMessages, type MessageDescriptor } from "@vintl/vintl";
import type { Nag, NagContext, NagStatus } from "@modrinth/moderation";
import { nags } from "@modrinth/moderation";
import { ButtonStyled, injectNotificationManager } from "@modrinth/ui";
import type { Project, User, Version } from "@modrinth/utils";
import { defineMessages, useVIntl, type MessageDescriptor } from "@vintl/vintl";
import type { Component } from "vue";
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
const { addNotification } = injectNotificationManager();
interface Tags {
rejectedStatuses: string[];
@@ -433,14 +434,12 @@ async function acceptInvite(): Promise<void> {
await acceptTeamInvite(props.project.team);
await updateMembers();
addNotification({
group: "main",
title: formatMessage(messages.success),
text: formatMessage(messages.successJoin),
type: "success",
});
} catch (error) {
addNotification({
group: "main",
title: formatMessage(messages.error),
text: formatMessage(messages.errorJoin),
type: "error",
@@ -456,14 +455,12 @@ async function declineInvite(): Promise<void> {
await removeTeamMember(props.project.team, props.auth.user.id);
await updateMembers();
addNotification({
group: "main",
title: formatMessage(messages.success),
text: formatMessage(messages.successDecline),
type: "success",
});
} catch (error) {
addNotification({
group: "main",
title: formatMessage(messages.error),
text: formatMessage(messages.errorDecline),
type: "error",

View File

@@ -118,22 +118,25 @@
</template>
<script setup lang="ts">
import dayjs from "dayjs";
import {
Avatar,
useRelativeTime,
OverflowMenu,
type OverflowMenuOption,
ButtonStyled,
} from "@modrinth/ui";
import {
EllipsisVerticalIcon,
OrganizationIcon,
EyeIcon,
ClipboardCopyIcon,
EllipsisVerticalIcon,
EyeIcon,
LinkIcon,
OrganizationIcon,
} from "@modrinth/assets";
import type { ExtendedDelphiReport } from "@modrinth/moderation";
import {
Avatar,
ButtonStyled,
injectNotificationManager,
OverflowMenu,
useRelativeTime,
type OverflowMenuOption,
} from "@modrinth/ui";
import dayjs from "dayjs";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
report: ExtendedDelphiReport;

View File

@@ -135,28 +135,31 @@
</template>
<script setup lang="ts">
import {
Avatar,
useRelativeTime,
OverflowMenu,
type OverflowMenuOption,
CollapsibleRegion,
ButtonStyled,
} from "@modrinth/ui";
import {
EllipsisVerticalIcon,
OrganizationIcon,
EyeIcon,
ClipboardCopyIcon,
EllipsisVerticalIcon,
EyeIcon,
LinkIcon,
OrganizationIcon,
} from "@modrinth/assets";
import {
type ExtendedReport,
reportQuickReplies,
type ReportQuickReply,
} from "@modrinth/moderation";
import {
Avatar,
ButtonStyled,
CollapsibleRegion,
injectNotificationManager,
OverflowMenu,
type OverflowMenuOption,
useRelativeTime,
} from "@modrinth/ui";
import ChevronDownIcon from "../servers/icons/ChevronDownIcon.vue";
import ReportThread from "../thread/ReportThread.vue";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
report: ExtendedReport;
}>();

View File

@@ -335,66 +335,67 @@
<script lang="ts" setup>
import {
LeftArrowIcon,
RightArrowIcon,
DropdownIcon,
XIcon,
ScaleIcon,
ListBulletedIcon,
FileTextIcon,
BrushCleaningIcon,
CheckIcon,
KeyboardIcon,
DropdownIcon,
EyeOffIcon,
FileTextIcon,
KeyboardIcon,
LeftArrowIcon,
ListBulletedIcon,
RightArrowIcon,
ScaleIcon,
ToggleLeftIcon,
ToggleRightIcon,
XIcon,
} from "@modrinth/assets";
import {
type Action,
type ButtonAction,
type ConditionalButtonAction,
type DropdownAction,
type MultiSelectChipsAction,
type Stage,
type ToggleAction,
checklist,
getActionIdForStage,
initializeActionState,
getActionMessage,
findMatchingVariant,
processMessage,
getVisibleInputs,
serializeActionStates,
deserializeActionStates,
kebabToTitleCase,
flattenProjectVariables,
expandVariables,
finalPermissionMessages,
findMatchingVariant,
flattenProjectVariables,
getActionIdForStage,
getActionMessage,
getVisibleInputs,
handleKeybind,
initializeActionState,
kebabToTitleCase,
keybinds,
processMessage,
serializeActionStates,
} from "@modrinth/moderation";
import {
ButtonStyled,
Collapsible,
OverflowMenu,
type OverflowMenuOption,
Checkbox,
Collapsible,
DropdownSelect,
MarkdownEditor,
OverflowMenu,
type OverflowMenuOption,
injectNotificationManager,
} from "@modrinth/ui";
import {
type Project,
renderHighlightedString,
type ModerationJudgements,
type ModerationModpackItem,
type Project,
type ProjectStatus,
renderHighlightedString,
} from "@modrinth/utils";
import { computedAsync, useLocalStorage } from "@vueuse/core";
import {
type Action,
type MultiSelectChipsAction,
type DropdownAction,
type ButtonAction,
type ToggleAction,
type ConditionalButtonAction,
type Stage,
finalPermissionMessages,
} from "@modrinth/moderation";
import ModpackPermissionsFlow from "./ModpackPermissionsFlow.vue";
import KeybindsModal from "./ChecklistKeybindsModal.vue";
import { useModerationStore } from "~/store/moderation.ts";
import KeybindsModal from "./ChecklistKeybindsModal.vue";
import ModpackPermissionsFlow from "./ModpackPermissionsFlow.vue";
const { addNotification } = injectNotificationManager();
const keybindsModal = ref<InstanceType<typeof KeybindsModal>>();

View File

@@ -42,12 +42,14 @@
</template>
<script setup lang="ts">
import { ref, nextTick, computed } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { IssuesIcon, PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import { ModrinthServersFetchError, type ServerBackup } from "@modrinth/utils";
import { computed, nextTick, ref } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();

View File

@@ -45,12 +45,14 @@
</template>
<script setup lang="ts">
import { ref, nextTick, computed } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { SpinnerIcon, SaveIcon, XIcon, IssuesIcon } from "@modrinth/assets";
import { IssuesIcon, SaveIcon, SpinnerIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import type { Backup } from "@modrinth/utils";
import { computed, nextTick, ref } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();

View File

@@ -17,12 +17,14 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ConfirmModal, NewModal } from "@modrinth/ui";
import { ConfirmModal, injectNotificationManager, NewModal } from "@modrinth/ui";
import type { Backup } from "@modrinth/utils";
import { ref } from "vue";
import BackupItem from "~/components/ui/servers/BackupItem.vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();

View File

@@ -56,11 +56,13 @@
</template>
<script setup lang="ts">
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { XIcon, SaveIcon } from "@modrinth/assets";
import { ref, computed } from "vue";
import { SaveIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import { computed, ref } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -110,7 +112,6 @@ const fetchSettings = async () => {
} catch (error) {
console.error("Error fetching backup settings:", error);
addNotification({
group: "server",
title: "Error",
text: "Failed to load backup settings",
type: "error",
@@ -135,7 +136,6 @@ const saveSettings = async () => {
};
addNotification({
group: "server",
title: "Success",
text: "Backup settings updated successfully",
type: "success",
@@ -145,7 +145,6 @@ const saveSettings = async () => {
} catch (error) {
console.error("Error saving backup settings:", error);
addNotification({
group: "server",
title: "Error",
text: "Failed to save backup settings",
type: "error",

View File

@@ -101,11 +101,13 @@
</template>
<script setup lang="ts">
import { FolderOpenIcon, CheckCircleIcon, XCircleIcon } from "@modrinth/assets";
import { ButtonStyled } from "@modrinth/ui";
import { ref, computed, watch, nextTick } from "vue";
import { CheckCircleIcon, FolderOpenIcon, XCircleIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager } from "@modrinth/ui";
import { computed, nextTick, ref, watch } from "vue";
import { FSModule } from "~/composables/servers/modules/fs.ts";
const { addNotification } = injectNotificationManager();
interface UploadItem {
file: File;
progress: number;
@@ -282,7 +284,6 @@ const uploadFile = async (file: File) => {
if (error instanceof Error && error.message !== "Upload cancelled") {
addNotification({
group: "files",
title: "Upload failed",
text: `Failed to upload ${file.name}`,
type: "error",

View File

@@ -67,11 +67,13 @@
</template>
<script setup lang="ts">
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { DownloadIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import { ModrinthServersFetchError } from "@modrinth/utils";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
project: any;
@@ -112,14 +114,12 @@ const handleReinstall = async () => {
} catch (error) {
if (error instanceof ModrinthServersFetchError && error.statusCode === 429) {
addNotification({
group: "server",
title: "Cannot reinstall server",
text: "You are being rate limited. Please try again later.",
type: "error",
});
} else {
addNotification({
group: "server",
title: "Reinstall Failed",
text: "An unexpected error occurred while reinstalling. Please try again later.",
type: "error",

View File

@@ -144,18 +144,20 @@
</template>
<script setup lang="ts">
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui";
import {
UploadIcon,
RightArrowIcon,
XIcon,
ServerIcon,
ArrowBigRightDashIcon,
RightArrowIcon,
ServerIcon,
UploadIcon,
XIcon,
} from "@modrinth/assets";
import { BackupWarning, ButtonStyled, injectNotificationManager, NewModal } from "@modrinth/ui";
import { formatBytes, ModrinthServersFetchError } from "@modrinth/utils";
import { onMounted, onUnmounted } from "vue";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
import type { ModrinthServer } from "~/composables/servers/modrinth-servers";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
const { addNotification } = injectNotificationManager();
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (isLoading.value) {
@@ -250,7 +252,6 @@ const handleReinstall = async () => {
if (!mrpackFile.value) {
addNotification({
group: "server",
title: "No file selected",
text: "Choose a .mrpack file before installing.",
type: "error",
@@ -301,14 +302,12 @@ const handleReinstall = async () => {
} catch (error) {
if (error instanceof ModrinthServersFetchError && error.statusCode === 429) {
addNotification({
group: "server",
title: "Cannot upload and install modpack to server",
text: "You are being rate limited. Please try again later.",
type: "error",
});
} else {
addNotification({
group: "server",
title: "Modpack upload and install failed",
text: "An unexpected error occurred while uploading/installing. Please try again later.",
type: "error",

View File

@@ -197,13 +197,20 @@
</template>
<script setup lang="ts">
import { BackupWarning, ButtonStyled, NewModal, Toggle } from "@modrinth/ui";
import { DropdownIcon, RightArrowIcon, ServerIcon, XIcon } from "@modrinth/assets";
import { $fetch } from "ofetch";
import {
BackupWarning,
ButtonStyled,
injectNotificationManager,
NewModal,
Toggle,
} from "@modrinth/ui";
import { type Loaders, ModrinthServersFetchError } from "@modrinth/utils";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
import { $fetch } from "ofetch";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
interface LoaderVersion {
@@ -475,14 +482,12 @@ const handleReinstall = async () => {
} catch (error) {
if (error instanceof ModrinthServersFetchError && (error as any)?.statusCode === 429) {
addNotification({
group: "server",
title: "Cannot reinstall server",
text: "You are being rate limited. Please try again later.",
type: "error",
});
} else {
addNotification({
group: "server",
title: "Reinstall Failed",
text: "An unexpected error occurred while reinstalling. Please try again later.",
type: "error",

View File

@@ -20,8 +20,11 @@
<script setup lang="ts">
import { LinkIcon } from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import { useStorage } from "@vueuse/core";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
subdomain: string;
noSeparator?: boolean;
@@ -30,7 +33,6 @@ const props = defineProps<{
const copySubdomain = () => {
navigator.clipboard.writeText(props.subdomain + ".modrinth.gg");
addNotification({
group: "servers",
title: "Custom URL copied",
text: "Your server's URL has been copied to your clipboard.",
type: "success",

View File

@@ -1,11 +1,18 @@
<script setup lang="ts">
import { Accordion, ButtonStyled, NewModal, ServerNotice, TagItem } from "@modrinth/ui";
import { PlusIcon, XIcon } from "@modrinth/assets";
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
import {
Accordion,
ButtonStyled,
injectNotificationManager,
NewModal,
ServerNotice,
TagItem,
} from "@modrinth/ui";
import { type ServerNotice as ServerNoticeType } from "@modrinth/utils";
import { ref } from "vue";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
const app = useNuxtApp() as unknown as { $notify: any };
const { addNotification } = injectNotificationManager();
const modal = ref<InstanceType<typeof NewModal>>();
@@ -39,16 +46,14 @@ async function assign(server: boolean = true) {
method: "PUT",
},
).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error assigning notice",
text: err,
type: "error",
});
});
} else {
app.$notify({
group: "main",
addNotification({
title: "Error assigning notice",
text: "No server or node specified",
type: "error",
@@ -64,8 +69,7 @@ async function unassignDetect() {
const node = assignedNodes.value.some((assigned) => assigned.id === input);
if (!server && !node) {
app.$notify({
group: "main",
addNotification({
title: "Error unassigning notice",
text: "ID is not an assigned server or node",
type: "error",
@@ -84,8 +88,7 @@ async function unassign(id: string, server: boolean = true) {
method: "PUT",
},
).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error unassigning notice",
text: err,
type: "error",

View File

@@ -251,23 +251,25 @@
</template>
<script setup>
import { CopyCode, OverflowMenu, MarkdownEditor } from "@modrinth/ui";
import {
DropdownIcon,
ReplyIcon,
SendIcon,
CheckCircleIcon,
XIcon,
EyeOffIcon,
CheckIcon,
DropdownIcon,
EyeOffIcon,
ReplyIcon,
ScaleIcon,
SendIcon,
XIcon,
} from "@modrinth/assets";
import { useImageUpload } from "~/composables/image-upload.ts";
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
import { isStaff } from "~/helpers/users.js";
import { isApproved, isRejected } from "~/helpers/projects.js";
import Modal from "~/components/ui/Modal.vue";
import { CopyCode, MarkdownEditor, OverflowMenu, injectNotificationManager } from "@modrinth/ui";
import Checkbox from "~/components/ui/Checkbox.vue";
import Modal from "~/components/ui/Modal.vue";
import ThreadMessage from "~/components/ui/thread/ThreadMessage.vue";
import { useImageUpload } from "~/composables/image-upload.ts";
import { isApproved, isRejected } from "~/helpers/projects.js";
import { isStaff } from "~/helpers/users.js";
const { addNotification } = injectNotificationManager();
const props = defineProps({
thread: {
@@ -388,8 +390,7 @@ async function sendReply(status = null, privateMessage = false) {
props.setStatus(status);
}
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "Error sending message",
text: err.data ? err.data.description : err,
type: "error",
@@ -411,8 +412,7 @@ async function closeReport(reply) {
});
await updateThreadLocal();
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "Error closing report",
text: err.data ? err.data.description : err,
type: "error",
@@ -430,8 +430,7 @@ async function reopenReport() {
});
await updateThreadLocal();
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "Error reopening report",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -110,13 +110,15 @@
</template>
<script setup lang="ts">
import { CopyCode, MarkdownEditor, ButtonStyled } from "@modrinth/ui";
import { ReplyIcon, SendIcon, CheckCircleIcon, ScaleIcon } from "@modrinth/assets";
import type { Thread, Report, User, ThreadMessage as TypeThreadMessage } from "@modrinth/utils";
import { CheckCircleIcon, ReplyIcon, ScaleIcon, SendIcon } from "@modrinth/assets";
import { ButtonStyled, CopyCode, injectNotificationManager, MarkdownEditor } from "@modrinth/ui";
import type { Report, Thread, ThreadMessage as TypeThreadMessage, User } from "@modrinth/utils";
import dayjs from "dayjs";
import ThreadMessage from "./ThreadMessage.vue";
import { useImageUpload } from "~/composables/image-upload.ts";
import { isStaff } from "~/helpers/users.js";
import ThreadMessage from "./ThreadMessage.vue";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
thread: Thread;

View File

@@ -1,3 +1,5 @@
import { injectNotificationManager } from "@modrinth/ui";
export const useAuth = async (oldToken = null) => {
const auth = useState("auth", () => ({
user: null,
@@ -128,9 +130,8 @@ export const removeAuthProvider = async (provider) => {
});
await useAuth(auth.value.token);
} catch (err) {
const data = useNuxtApp();
data.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: err.data.description,
type: "error",

View File

@@ -1,38 +0,0 @@
export const useNotifications = () => useState("notifications", () => []);
export const addNotification = (notification) => {
const notifications = useNotifications();
const existingNotif = notifications.value.find(
(x) =>
x.text === notification.text &&
x.title === notification.title &&
x.type === notification.type,
);
if (existingNotif) {
setNotificationTimer(existingNotif);
existingNotif.count++;
return;
}
notification.id = new Date();
notification.count = 1;
setNotificationTimer(notification);
notifications.value.push(notification);
};
export const setNotificationTimer = (notification) => {
if (!notification) return;
const notifications = useNotifications();
if (notification.timer) {
clearTimeout(notification.timer);
}
notification.timer = setTimeout(() => {
notifications.value.splice(notifications.value.indexOf(notification), 1);
}, 30000);
};

View File

@@ -1,18 +1,20 @@
import { ModrinthServerError } 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 {
GeneralModule,
ContentModule,
BackupsModule,
ContentModule,
FSModule,
GeneralModule,
NetworkModule,
StartupModule,
WSModule,
FSModule,
} from "./modules/index.ts";
export function handleError(err: any) {
const { addNotification } = injectNotificationManager();
if (err instanceof ModrinthServerError && err.v1Error) {
addNotification({
title: err.v1Error?.context ?? `An error occurred`,

View File

@@ -1,3 +1,5 @@
import { injectNotificationManager } from "@modrinth/ui";
type AsyncFunction<TArgs extends any[], TResult> = (...args: TArgs) => Promise<TResult>;
type ErrorFunction = (err: any) => void | Promise<void>;
type VoidFunction = () => void | Promise<void>;
@@ -9,8 +11,8 @@ type useClientTry = <TArgs extends any[], TResult>(
) => (...args: TArgs) => Promise<TResult | undefined>;
const defaultOnError: ErrorFunction = (error) => {
const { addNotification } = injectNotificationManager();
addNotification({
group: "main",
title: "An error occurred",
text: error?.data?.description || error.message || error || "Unknown error",
type: "error",

View File

@@ -135,7 +135,8 @@ export const userFollowProject = async (project) => {
}
};
export const resendVerifyEmail = async () => {
const app = useNuxtApp();
// const { injectNotificationManager } = await import("@modrinth/ui");
// const { addNotification } = injectNotificationManager();
startLoading();
try {
@@ -144,15 +145,13 @@ export const resendVerifyEmail = async () => {
});
const auth = await useAuth();
app.$notify({
group: "main",
addNotification({
title: "Email sent",
text: `An email with a link to verify your account has been sent to ${auth.value.user.email}.`,
type: "success",
});
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data.description,
type: "error",

View File

@@ -1,19 +1,14 @@
import { useNuxtApp } from "#imports";
import { injectNotificationManager } from "@modrinth/ui";
import type { Organization, Project, Report, User, Version } from "@modrinth/utils";
// TODO: There needs to be a standardized way to get these types, eg; @modrinth/types generated from api schema. Later problem.
type Project = { id: string };
type Version = { id: string; project_id: string };
type Report = { id: string; item_type: "project" | "user" | "version"; item_id: string };
type Thread = { id: string };
type User = { id: string };
type Organization = { id: string };
export type NotificationAction = {
export type PlatformNotificationAction = {
title: string;
action_route: [string, string];
};
export type NotificationBody = {
export type PlatformNotificationBody = {
project_id?: string;
version_id?: string;
report_id?: string;
@@ -22,7 +17,7 @@ export type NotificationBody = {
organization_id?: string;
};
export type Notification = {
export type PlatformNotification = {
id: string;
user_id: string;
type: "project_update" | "team_invite" | "status_change" | "moderator_message";
@@ -31,10 +26,10 @@ export type Notification = {
link: string;
read: boolean;
created: string;
actions: NotificationAction[];
body?: NotificationBody;
actions: PlatformNotificationAction[];
body?: PlatformNotificationBody;
extra_data?: Record<string, unknown>;
grouped_notifs?: Notification[];
grouped_notifs?: PlatformNotification[];
};
async function getBulk<T extends { id: string }>(
@@ -55,8 +50,8 @@ async function getBulk<T extends { id: string }>(
}
export async function fetchExtraNotificationData(
notifications: Notification[],
): Promise<Notification[]> {
notifications: PlatformNotification[],
): Promise<PlatformNotification[]> {
const bulk = {
projects: [] as string[],
reports: [] as string[],
@@ -133,8 +128,8 @@ export async function fetchExtraNotificationData(
return notifications;
}
export function groupNotifications(notifications: Notification[]): Notification[] {
const grouped: Notification[] = [];
export function groupNotifications(notifications: PlatformNotification[]): PlatformNotification[] {
const grouped: PlatformNotification[] = [];
for (let i = 0; i < notifications.length; i++) {
const current = notifications[i];
const next = notifications[i + 1];
@@ -154,18 +149,18 @@ export function groupNotifications(notifications: Notification[]): Notification[
return grouped;
}
function isSimilar(a: Notification, b: Notification | undefined): boolean {
function isSimilar(a: PlatformNotification, b: PlatformNotification | undefined): boolean {
return !!a?.body?.project_id && a.body!.project_id === b?.body?.project_id;
}
export async function markAsRead(
ids: string[],
): Promise<(notifications: Notification[]) => Notification[]> {
): Promise<(notifications: PlatformNotification[]) => PlatformNotification[]> {
try {
await useBaseFetch(`notifications?ids=${JSON.stringify([...new Set(ids)])}`, {
method: "PATCH",
});
return (notifications: Notification[]) => {
return (notifications: PlatformNotification[]) => {
const newNotifs = notifications ?? [];
newNotifs.forEach((n) => {
if (ids.includes(n.id)) n.read = true;
@@ -173,9 +168,8 @@ export async function markAsRead(
return newNotifs;
};
} catch (err: any) {
const app: any = useNuxtApp();
app.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Error marking notification as read",
text: err?.data?.description ?? err,
type: "error",

View File

@@ -666,65 +666,65 @@
</template>
<script setup>
import {
ModrinthIcon,
ArrowBigUpDashIcon,
BookmarkIcon,
ServerIcon,
LogInIcon,
DownloadIcon,
LibraryIcon,
XIcon,
IssuesIcon,
ReportIcon,
CompassIcon,
HamburgerIcon,
SearchIcon,
BellIcon,
SettingsIcon,
BlueskyIcon,
BookmarkIcon,
BoxIcon,
BracesIcon,
ChartIcon,
CollectionIcon,
CompassIcon,
CurrencyIcon,
DiscordIcon,
DownloadIcon,
DropdownIcon,
GithubIcon,
GlassesIcon,
HamburgerIcon,
HomeIcon,
IssuesIcon,
LibraryIcon,
LogInIcon,
LogOutIcon,
MastodonIcon,
ModrinthIcon,
MoonIcon,
SunIcon,
OrganizationIcon,
PackageOpenIcon,
PaintbrushIcon,
PlugIcon,
PlusIcon,
DropdownIcon,
LogOutIcon,
ChartIcon,
BoxIcon,
CollectionIcon,
OrganizationIcon,
UserIcon,
CurrencyIcon,
BracesIcon,
GlassesIcon,
PaintbrushIcon,
PackageOpenIcon,
DiscordIcon,
BlueskyIcon,
TwitterIcon,
MastodonIcon,
GithubIcon,
ReportIcon,
ScaleIcon,
SearchIcon,
ServerIcon,
SettingsIcon,
SunIcon,
TwitterIcon,
UserIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
Button,
ButtonStyled,
commonMessages,
injectNotificationManager,
OverflowMenu,
PagewideBanner,
Avatar,
commonMessages,
} from "@modrinth/ui";
import { isAdmin, isStaff } from "@modrinth/utils";
import { errors as generatedStateErrors } from "~/generated/state.json";
import ModalCreation from "~/components/ui/ModalCreation.vue";
import { getProjectTypeMessage } from "~/utils/i18n-project-type.ts";
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
import ModalCreation from "~/components/ui/ModalCreation.vue";
import OrganizationCreateModal from "~/components/ui/OrganizationCreateModal.vue";
import TeleportOverflowMenu from "~/components/ui/servers/TeleportOverflowMenu.vue";
import { errors as generatedStateErrors } from "~/generated/state.json";
import { getProjectTypeMessage } from "~/utils/i18n-project-type.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const app = useNuxtApp();
const auth = await useAuth();
const user = await useUser();
@@ -1079,15 +1079,13 @@ function developerModeIncrement() {
developerModeCounter.value = 0;
saveFeatureFlags();
if (flags.value.developerMode) {
app.$notify({
group: "main",
addNotification({
title: "Developer mode activated",
text: "Developer mode has been enabled",
type: "success",
});
} else {
app.$notify({
group: "main",
addNotification({
title: "Developer mode deactivated",
text: "Developer mode has been disabled",
type: "success",

View File

@@ -890,6 +890,7 @@
</template>
<script setup>
import { navigateTo } from "#app";
import {
BookmarkIcon,
BookTextIcon,
@@ -906,6 +907,7 @@ import {
HeartIcon,
InfoIcon,
LinkIcon as LinksIcon,
ModrinthIcon,
MoreVerticalIcon,
PlusIcon,
ReportIcon,
@@ -917,13 +919,13 @@ import {
UsersIcon,
VersionIcon,
WrenchIcon,
ModrinthIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
ButtonStyled,
Checkbox,
injectNotificationManager,
NewModal,
OverflowMenu,
PopoutMenu,
@@ -935,36 +937,37 @@ import {
ProjectSidebarLinks,
ProjectStatusBadge,
ScrollablePanel,
TagItem,
ServersPromo,
TagItem,
useRelativeTime,
} from "@modrinth/ui";
import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.vue";
import { formatCategory, formatProjectType, renderString } from "@modrinth/utils";
import { useLocalStorage } from "@vueuse/core";
import dayjs from "dayjs";
import { Tooltip } from "floating-vue";
import { useLocalStorage } from "@vueuse/core";
import { navigateTo } from "#app";
import Accordion from "~/components/ui/Accordion.vue";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import AutomaticAccordion from "~/components/ui/AutomaticAccordion.vue";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
import CollectionCreateModal from "~/components/ui/CollectionCreateModal.vue";
import MessageBanner from "~/components/ui/MessageBanner.vue";
import ModerationChecklist from "~/components/ui/moderation/checklist/ModerationChecklist.vue";
import NavStack from "~/components/ui/NavStack.vue";
import NavStackItem from "~/components/ui/NavStackItem.vue";
import NavTabs from "~/components/ui/NavTabs.vue";
import ProjectMemberHeader from "~/components/ui/ProjectMemberHeader.vue";
import { userCollectProject } from "~/composables/user.js";
import { reportProject } from "~/utils/report-helpers.ts";
import { saveFeatureFlags } from "~/composables/featureFlags.ts";
import ModerationChecklist from "~/components/ui/moderation/checklist/ModerationChecklist.vue";
import { userCollectProject } from "~/composables/user.js";
import { useModerationStore } from "~/store/moderation.ts";
import { reportProject } from "~/utils/report-helpers.ts";
const data = useNuxtApp();
const route = useNativeRoute();
const config = useRuntimeConfig();
const moderationStore = useModerationStore();
const notifications = injectNotificationManager();
const { addNotification } = notifications;
const auth = await useAuth();
const user = await useUser();
@@ -974,7 +977,6 @@ const flags = useFeatureFlags();
const cosmetics = useCosmetics();
const { formatMessage } = useVIntl();
const { setVisible } = useNotificationRightwards();
const settingsModal = ref();
const downloadModal = ref();
@@ -1425,8 +1427,7 @@ async function setProcessing() {
project.value.status = "processing";
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1459,8 +1460,7 @@ async function patchProject(resData, quiet = false) {
result = true;
if (!quiet) {
data.$notify({
group: "main",
addNotification({
title: "Project updated",
text: "Your project has been updated.",
type: "success",
@@ -1468,8 +1468,7 @@ async function patchProject(resData, quiet = false) {
window.scrollTo({ top: 0, behavior: "smooth" });
}
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1498,15 +1497,13 @@ async function patchIcon(icon) {
);
await resetProject();
result = true;
data.$notify({
group: "main",
addNotification({
title: "Project icon updated",
text: "Your project's icon has been updated.",
type: "success",
});
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1555,11 +1552,15 @@ const collapsedModerationChecklist = useLocalStorage("collapsed-moderation-check
watch(
showModerationChecklist,
(newValue) => {
setVisible(newValue);
notifications.setNotificationLocation(newValue ? "left" : "right");
},
{ immediate: true },
);
onUnmounted(() => {
notifications.setNotificationLocation("right");
});
if (import.meta.client && history && history.state && history.state.showChecklist) {
showModerationChecklist.value = true;
}

View File

@@ -278,26 +278,26 @@
<script setup>
import {
PlusIcon,
CalendarIcon,
ContractIcon,
EditIcon,
TrashIcon,
ExpandIcon,
ExternalIcon,
ImageIcon,
InfoIcon,
LeftArrowIcon,
PlusIcon,
RightArrowIcon,
SaveIcon,
StarIcon,
XIcon,
RightArrowIcon,
LeftArrowIcon,
ExternalIcon,
ExpandIcon,
ContractIcon,
UploadIcon,
InfoIcon,
ImageIcon,
TransferIcon,
TrashIcon,
UploadIcon,
XIcon,
} from "@modrinth/assets";
import { ConfirmModal } from "@modrinth/ui";
import FileInput from "~/components/ui/FileInput.vue";
import { ConfirmModal, injectNotificationManager } from "@modrinth/ui";
import DropArea from "~/components/ui/DropArea.vue";
import FileInput from "~/components/ui/FileInput.vue";
import Modal from "~/components/ui/Modal.vue";
import { isPermission } from "~/utils/permissions.ts";
@@ -425,6 +425,8 @@ export default defineNuxtComponent({
this.shouldPreventActions = true;
startLoading();
const { addNotification } = injectNotificationManager();
try {
let url = `project/${this.project.id}/gallery?ext=${
this.editFile
@@ -450,8 +452,7 @@ export default defineNuxtComponent({
this.$refs.modal_edit_item.hide();
} catch (err) {
this.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -465,6 +466,8 @@ export default defineNuxtComponent({
this.shouldPreventActions = true;
startLoading();
const { addNotification } = injectNotificationManager();
try {
let url = `project/${this.project.id}/gallery?url=${encodeURIComponent(
this.project.gallery[this.editIndex].url,
@@ -487,8 +490,7 @@ export default defineNuxtComponent({
await this.resetProject();
this.$refs.modal_edit_item.hide();
} catch (err) {
this.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -501,6 +503,8 @@ export default defineNuxtComponent({
async deleteGalleryImage() {
startLoading();
const { addNotification } = injectNotificationManager();
try {
await useBaseFetch(
`project/${this.project.id}/gallery?url=${encodeURIComponent(
@@ -513,8 +517,7 @@ export default defineNuxtComponent({
await this.resetProject();
} catch (err) {
this.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -99,8 +99,8 @@
</div>
</template>
<script setup>
import { XIcon, CheckIcon, IssuesIcon } from "@modrinth/assets";
import { Badge } from "@modrinth/ui";
import { CheckIcon, IssuesIcon, XIcon } from "@modrinth/assets";
import { Badge, injectNotificationManager } from "@modrinth/ui";
import ConversationThread from "~/components/ui/thread/ConversationThread.vue";
import {
getProjectLink,
@@ -111,6 +111,7 @@ import {
isUnderReview,
} from "~/helpers/projects.js";
const { addNotification } = injectNotificationManager();
const props = defineProps({
project: {
type: Object,
@@ -131,7 +132,6 @@ const props = defineProps({
},
});
const app = useNuxtApp();
const auth = await useAuth();
const { data: thread } = await useAsyncData(`thread/${props.project.thread_id}`, () =>
@@ -153,8 +153,7 @@ async function setStatus(status) {
await props.resetProject();
thread.value = await useBaseFetch(`thread/${thread.value.id}`);
} catch (err) {
app.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -243,21 +243,23 @@
</template>
<script setup>
import { formatProjectStatus, formatProjectType } from "@modrinth/utils";
import {
UploadIcon,
CheckIcon,
IssuesIcon,
SaveIcon,
TrashIcon,
UploadIcon,
XIcon,
IssuesIcon,
CheckIcon,
TriangleAlertIcon,
} from "@modrinth/assets";
import { Avatar, ConfirmModal, injectNotificationManager } from "@modrinth/ui";
import { formatProjectStatus, formatProjectType } from "@modrinth/utils";
import { Multiselect } from "vue-multiselect";
import { ConfirmModal, Avatar } from "@modrinth/ui";
import { MIN_SUMMARY_CHARS } from "@modrinth/moderation";
import FileInput from "~/components/ui/FileInput.vue";
const { addNotification } = injectNotificationManager();
const props = defineProps({
project: {
type: Object,
@@ -398,7 +400,6 @@ const deleteProject = async () => {
await initUserProjects();
await router.push("/dashboard/projects");
addNotification({
group: "main",
title: "Project deleted",
text: "Your project has been deleted.",
type: "success",
@@ -417,7 +418,6 @@ const deleteIcon = async () => {
});
await props.resetProject();
addNotification({
group: "main",
title: "Project icon removed",
text: "Your project's icon has been removed.",
type: "success",

View File

@@ -518,21 +518,30 @@
</template>
<script setup>
import { Multiselect } from "vue-multiselect";
import {
TransferIcon,
CheckIcon,
UsersIcon,
DropdownIcon,
SaveIcon,
UserPlusIcon,
UserXIcon,
OrganizationIcon,
CrownIcon,
DropdownIcon,
OrganizationIcon,
SaveIcon,
TransferIcon,
UserPlusIcon,
UsersIcon,
UserXIcon,
} from "@modrinth/assets";
import { Avatar, Badge, Card, Checkbox, ConfirmModal } from "@modrinth/ui";
import {
Avatar,
Badge,
Card,
Checkbox,
ConfirmModal,
injectNotificationManager,
} from "@modrinth/ui";
import { Multiselect } from "vue-multiselect";
import { removeSelfFromTeam } from "~/helpers/teams.js";
const { addNotification } = injectNotificationManager();
const props = defineProps({
project: {
type: Object,
@@ -654,7 +663,6 @@ const onAddToOrg = useClientTry(async () => {
await updateMembers();
addNotification({
group: "main",
title: "Project transferred",
text: "Your project has been transferred to the organization.",
type: "success",
@@ -675,7 +683,6 @@ const onRemoveFromOrg = useClientTry(async () => {
await updateMembers();
addNotification({
group: "main",
title: "Project removed",
text: "Your project has been removed from the organization.",
type: "success",
@@ -703,7 +710,6 @@ const inviteTeamMember = async () => {
await updateMembers();
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -726,7 +732,6 @@ const removeTeamMember = async (index) => {
await updateMembers();
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -760,14 +765,12 @@ const updateTeamMember = async (index) => {
);
await updateMembers();
addNotification({
group: "main",
title: "Member(s) updated",
text: "Your project's member(s) has been updated.",
type: "success",
});
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -790,7 +793,6 @@ const transferOwnership = async (index) => {
await updateMembers();
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -837,7 +839,6 @@ async function updateOrgMember(index) {
await updateMembers();
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",

View File

@@ -631,45 +631,46 @@
</template>
<script>
import {
Avatar,
Badge,
CopyCode,
Checkbox,
ButtonStyled,
ConfirmModal,
MarkdownEditor,
} from "@modrinth/ui";
import {
FileIcon,
TrashIcon,
EditIcon,
BoxIcon,
ChevronRightIcon,
DownloadIcon,
StarIcon,
ReportIcon,
SaveIcon,
XIcon,
EditIcon,
FileIcon,
HashIcon,
PlusIcon,
TransferIcon,
UploadIcon,
BoxIcon,
ReportIcon,
RightArrowIcon,
ChevronRightIcon,
SaveIcon,
StarIcon,
TransferIcon,
TrashIcon,
UploadIcon,
XIcon,
} from "@modrinth/assets";
import { Multiselect } from "vue-multiselect";
import {
Avatar,
Badge,
ButtonStyled,
Checkbox,
ConfirmModal,
CopyCode,
injectNotificationManager,
MarkdownEditor,
} from "@modrinth/ui";
import { formatBytes, formatCategory } from "@modrinth/utils";
import { Multiselect } from "vue-multiselect";
import { useImageUpload } from "~/composables/image-upload.ts";
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
import { renderHighlightedString } from "~/helpers/highlight.js";
import { inferVersionInfo } from "~/helpers/infer.js";
import { createDataPackVersion } from "~/helpers/package.js";
import { renderHighlightedString } from "~/helpers/highlight.js";
import { reportVersion } from "~/utils/report-helpers.ts";
import { useImageUpload } from "~/composables/image-upload.ts";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
import Categories from "~/components/ui/search/Categories.vue";
import FileInput from "~/components/ui/FileInput.vue";
import Modal from "~/components/ui/Modal.vue";
import Categories from "~/components/ui/search/Categories.vue";
export default defineNuxtComponent({
components: {
@@ -997,8 +998,8 @@ export default defineNuxtComponent({
const project = await useBaseFetch(`project/${newDependencyId}`);
if (this.version.dependencies.some((dep) => project.id === dep.project_id)) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Dependency already added",
text: "You cannot add the same dependency twice.",
type: "error",
@@ -1022,8 +1023,8 @@ export default defineNuxtComponent({
const project = await useBaseFetch(`project/${version.project_id}`);
if (this.version.dependencies.some((dep) => version.id === dep.version_id)) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Dependency already added",
text: "You cannot add the same dependency twice.",
type: "error",
@@ -1050,8 +1051,8 @@ export default defineNuxtComponent({
this.newDependencyId = "";
} catch {
if (!hideErrors) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Invalid Dependency",
text: "The specified dependency could not be found",
type: "error",
@@ -1144,8 +1145,8 @@ export default defineNuxtComponent({
)}`,
);
} catch (err) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1169,8 +1170,8 @@ export default defineNuxtComponent({
try {
await this.createVersionRaw(this.version);
} catch (err) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -1293,15 +1294,15 @@ export default defineNuxtComponent({
this.$refs.modal_package_mod.hide();
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "Packaging Success",
text: "Your data pack was successfully packaged as a mod! Make sure to playtest to check for errors.",
type: "success",
});
} catch (err) {
this.$notify({
group: "main",
const { addNotification } = injectNotificationManager();
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -251,28 +251,31 @@
</div>
</template>
<script setup>
import {
CheckIcon,
CurrencyIcon,
ExternalIcon,
ModrinthPlusIcon,
ServerIcon,
UserIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
ButtonStyled,
CopyCode,
DropdownSelect,
injectNotificationManager,
NewModal,
Toggle,
useRelativeTime,
} from "@modrinth/ui";
import { formatCategory, formatPrice } from "@modrinth/utils";
import {
CheckIcon,
XIcon,
UserIcon,
ModrinthPlusIcon,
ServerIcon,
ExternalIcon,
CurrencyIcon,
} from "@modrinth/assets";
import dayjs from "dayjs";
import { products } from "~/generated/state.json";
import ModrinthServersIcon from "~/components/ui/servers/ModrinthServersIcon.vue";
import { products } from "~/generated/state.json";
const { addNotification } = injectNotificationManager();
const route = useRoute();
const vintl = useVIntl();

View File

@@ -258,31 +258,31 @@
</div>
</template>
<script setup lang="ts">
import { EditIcon, PlusIcon, SaveIcon, SettingsIcon, TrashIcon, XIcon } from "@modrinth/assets";
import {
CopyCode,
TagItem,
ButtonStyled,
ServerNotice,
commonMessages,
CopyCode,
injectNotificationManager,
NewModal,
ServerNotice,
TagItem,
TeleportDropdownMenu,
Toggle,
useRelativeTime,
} from "@modrinth/ui";
import { SettingsIcon, PlusIcon, SaveIcon, TrashIcon, EditIcon, XIcon } from "@modrinth/assets";
import dayjs from "dayjs";
import { useVIntl } from "@vintl/vintl";
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
import { computed } from "vue";
import { NOTICE_LEVELS } from "@modrinth/ui/src/utils/notices.ts";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import type { ServerNotice as ServerNoticeType } from "@modrinth/utils";
import { useVIntl } from "@vintl/vintl";
import dayjs from "dayjs";
import { computed } from "vue";
import AssignNoticeModal from "~/components/ui/servers/notice/AssignNoticeModal.vue";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const formatRelativeTime = useRelativeTime();
const app = useNuxtApp() as unknown as { $notify: any };
const notices = ref<ServerNoticeType[]>([]);
const createNoticeModal = ref<InstanceType<typeof NewModal>>();
const assignNoticeModal = ref<InstanceType<typeof AssignNoticeModal>>();
@@ -351,15 +351,13 @@ async function deleteNotice(notice: ServerNoticeType) {
method: "DELETE",
})
.then(() => {
app.$notify({
group: "main",
addNotification({
title: `Successfully deleted notice #${notice.id}`,
type: "success",
});
})
.catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error deleting notice",
text: err,
type: "error",
@@ -386,7 +384,6 @@ const noticeSubmitError = computed(() => {
function validateSubmission(message: string) {
if (noticeSubmitError.value) {
addNotification({
group: "main",
title: message,
text: noticeSubmitError.value,
type: "error",
@@ -416,8 +413,7 @@ async function saveChanges() {
: undefined,
},
}).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error saving changes to notice",
text: err,
type: "error",
@@ -447,8 +443,7 @@ async function createNotice() {
: undefined,
},
}).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error creating notice",
text: err,
type: "error",

View File

@@ -32,8 +32,10 @@
</div>
</template>
<script setup lang="ts">
import { ButtonStyled } from "@modrinth/ui";
import { MailIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const userEmail = ref("");
@@ -50,7 +52,6 @@ async function getUserFromEmail() {
} catch (err) {
console.error(err);
addNotification({
group: "main",
title: "An error occurred",
text: err.data.description,
type: "error",

View File

@@ -80,13 +80,14 @@
</template>
<script setup>
import { Button, Avatar, commonMessages } from "@modrinth/ui";
import { XIcon, CheckIcon } from "@modrinth/assets";
import { useBaseFetch } from "@/composables/fetch.js";
import { CheckIcon, XIcon } from "@modrinth/assets";
import { Avatar, Button, commonMessages, injectNotificationManager } from "@modrinth/ui";
import { useAuth } from "@/composables/auth.js";
import { useBaseFetch } from "@/composables/fetch.js";
import { useScopes } from "@/composables/auth/scopes.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const messages = defineMessages({
@@ -117,8 +118,6 @@ const messages = defineMessages({
},
});
const data = useNuxtApp();
const router = useNativeRoute();
const auth = await useAuth();
const { scopesToDefinitions } = useScopes();
@@ -196,8 +195,7 @@ const onAuthorize = async () => {
throw new Error(formatMessage(messages.noRedirectUrlError));
} catch {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -223,8 +221,7 @@ const onReject = async () => {
throw new Error(formatMessage(messages.noRedirectUrlError));
} catch {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -67,10 +67,11 @@
</div>
</template>
<script setup>
import { SendIcon, MailIcon, KeyIcon } from "@modrinth/assets";
import { commonMessages } from "@modrinth/ui";
import { KeyIcon, MailIcon, SendIcon } from "@modrinth/assets";
import { commonMessages, injectNotificationManager } from "@modrinth/ui";
import HCaptcha from "@/components/ui/HCaptcha.vue";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const methodChoiceMessages = defineMessages({
@@ -179,14 +180,12 @@ async function recovery() {
});
addNotification({
group: "main",
title: formatMessage(emailSentNotificationMessages.title),
text: formatMessage(emailSentNotificationMessages.text),
type: "success",
});
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -211,7 +210,6 @@ async function changePassword() {
});
addNotification({
group: "main",
title: formatMessage(passwordResetNotificationMessages.title),
text: formatMessage(passwordResetNotificationMessages.text),
type: "success",
@@ -219,7 +217,6 @@ async function changePassword() {
await navigateTo("/auth/sign-in");
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -130,19 +130,20 @@
<script setup>
import {
RightArrowIcon,
SSOGitHubIcon,
SSOMicrosoftIcon,
SSOSteamIcon,
SSOGoogleIcon,
SSODiscordIcon,
SSOGitLabIcon,
KeyIcon,
MailIcon,
RightArrowIcon,
SSODiscordIcon,
SSOGitHubIcon,
SSOGitLabIcon,
SSOGoogleIcon,
SSOMicrosoftIcon,
SSOSteamIcon,
} from "@modrinth/assets";
import { commonMessages } from "@modrinth/ui";
import { commonMessages, injectNotificationManager } from "@modrinth/ui";
import HCaptcha from "@/components/ui/HCaptcha.vue";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const messages = defineMessages({
@@ -232,7 +233,6 @@ async function beginPasswordSignIn() {
}
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -257,7 +257,6 @@ async function begin2FASignIn() {
await finishSignIn(res.session);
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -134,20 +134,21 @@
<script setup>
import {
RightArrowIcon,
UserIcon,
SSOGitHubIcon,
SSOMicrosoftIcon,
SSOGoogleIcon,
SSOSteamIcon,
SSODiscordIcon,
KeyIcon,
MailIcon,
RightArrowIcon,
SSODiscordIcon,
SSOGitHubIcon,
SSOGitLabIcon,
SSOGoogleIcon,
SSOMicrosoftIcon,
SSOSteamIcon,
UserIcon,
} from "@modrinth/assets";
import { Checkbox, commonMessages } from "@modrinth/ui";
import { Checkbox, commonMessages, injectNotificationManager } from "@modrinth/ui";
import HCaptcha from "@/components/ui/HCaptcha.vue";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const messages = defineMessages({
@@ -225,7 +226,6 @@ async function createAccount() {
try {
if (confirmPassword.value !== password.value) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: formatMessage({
id: "auth.sign-up.notification.password-mismatch.text",
@@ -262,7 +262,6 @@ async function createAccount() {
}
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -367,6 +367,7 @@ import {
BoxIcon,
CalendarIcon,
EditIcon,
GlobeIcon,
GridIcon,
ImageIcon,
LibraryIcon,
@@ -378,7 +379,6 @@ import {
UpdatedIcon,
UploadIcon,
XIcon,
GlobeIcon,
} from "@modrinth/assets";
import {
Avatar,
@@ -387,17 +387,17 @@ import {
ConfirmModal,
DropdownSelect,
FileInput,
injectNotificationManager,
PopoutMenu,
useRelativeTime,
} from "@modrinth/ui";
import { isAdmin } from "@modrinth/utils";
import UpToDate from "assets/images/illustrations/up_to_date.svg";
import { addNotification } from "~/composables/notifs.js";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
import NavRow from "~/components/ui/NavRow.vue";
import ProjectCard from "~/components/ui/ProjectCard.vue";
import AdPlaceholder from "~/components/ui/AdPlaceholder.vue";
const { addNotification } = injectNotificationManager();
const vintl = useVIntl();
const { formatMessage } = vintl;
const formatRelativeTime = useRelativeTime();
@@ -664,7 +664,6 @@ async function saveChanges() {
isEditing.value = false;
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err,
type: "error",
@@ -688,7 +687,6 @@ async function deleteCollection() {
}
} catch (err) {
addNotification({
group: "main",
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -99,7 +99,10 @@
import { ChevronRightIcon, HistoryIcon } from "@modrinth/assets";
import { Avatar } from "@modrinth/ui";
import NotificationItem from "~/components/ui/NotificationItem.vue";
import { fetchExtraNotificationData, groupNotifications } from "~/helpers/notifications.ts";
import {
fetchExtraNotificationData,
groupNotifications,
} from "~/helpers/platform-notifications.ts";
useHead({
title: "Dashboard - Modrinth",

View File

@@ -56,16 +56,16 @@
</div>
</template>
<script setup>
import { Button, Pagination, Chips } from "@modrinth/ui";
import { HistoryIcon, CheckCheckIcon } from "@modrinth/assets";
import { CheckCheckIcon, HistoryIcon } from "@modrinth/assets";
import { Button, Chips, Pagination } from "@modrinth/ui";
import { formatProjectType } from "@modrinth/utils";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
import NotificationItem from "~/components/ui/NotificationItem.vue";
import {
fetchExtraNotificationData,
groupNotifications,
markAsRead,
} from "~/helpers/notifications.ts";
import NotificationItem from "~/components/ui/NotificationItem.vue";
import Breadcrumbs from "~/components/ui/Breadcrumbs.vue";
} from "~/helpers/platform-notifications.ts";
useHead({
title: "Notifications - Modrinth",

View File

@@ -301,17 +301,16 @@
</template>
<script>
import { Multiselect } from "vue-multiselect";
import {
SettingsIcon,
TrashIcon,
PlusIcon,
XIcon,
IssuesIcon,
EditIcon,
IssuesIcon,
PlusIcon,
SaveIcon,
SettingsIcon,
SortAscIcon,
SortDescIcon,
TrashIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
@@ -320,8 +319,10 @@ import {
CopyCode,
ProjectStatusBadge,
commonMessages,
injectNotificationManager,
} from "@modrinth/ui";
import { formatProjectType } from "@modrinth/utils";
import { Multiselect } from "vue-multiselect";
import Modal from "~/components/ui/Modal.vue";
import ModalCreation from "~/components/ui/ModalCreation.vue";
@@ -444,6 +445,8 @@ export default defineNuxtComponent({
return sortedArray;
},
async bulkEditLinks() {
const { addNotification } = injectNotificationManager();
try {
const baseData = {
issues_url: this.editLinks.issues.clear ? null : this.editLinks.issues.val.trim(),
@@ -477,8 +480,7 @@ export default defineNuxtComponent({
);
this.$refs.editLinksModal.hide();
this.$notify({
group: "main",
addNotification({
title: "Success",
text: "Bulk edited selected project's links.",
type: "success",
@@ -494,8 +496,7 @@ export default defineNuxtComponent({
this.editLinks.wiki.clear = false;
this.editLinks.discord.clear = false;
} catch (e) {
this.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: e,
type: "error",

View File

@@ -149,10 +149,12 @@ import {
UnknownIcon,
XIcon,
} from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import { formatDate } from "@modrinth/utils";
import dayjs from "dayjs";
import { computed } from "vue";
const { addNotification } = injectNotificationManager();
const auth = await useAuth();
const minWithdraw = ref(0.01);
@@ -201,9 +203,7 @@ async function updateVenmo() {
});
await useAuth(auth.value.token);
} catch (err) {
const data = useNuxtApp();
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -94,13 +94,14 @@
</div>
</template>
<script setup>
import { XIcon, PayPalIcon, UnknownIcon } from "@modrinth/assets";
import { PayPalIcon, UnknownIcon, XIcon } from "@modrinth/assets";
import { Badge, Breadcrumbs, DropdownSelect, injectNotificationManager } from "@modrinth/ui";
import { capitalizeString, formatWallet } from "@modrinth/utils";
import { Badge, Breadcrumbs, DropdownSelect } from "@modrinth/ui";
import dayjs from "dayjs";
import TremendousIcon from "~/assets/images/external/tremendous.svg?component";
import VenmoIcon from "~/assets/images/external/venmo-small.svg?component";
const { addNotification } = injectNotificationManager();
const vintl = useVIntl();
const { formatMessage } = vintl;
@@ -108,7 +109,6 @@ useHead({
title: "Transfer history - Modrinth",
});
const data = await useNuxtApp();
const auth = await useAuth();
const { data: payouts, refresh } = await useAsyncData(`payout`, () =>
@@ -155,8 +155,7 @@ async function cancelPayout(id) {
await refresh();
await useAuth(auth.value.token);
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -187,20 +187,21 @@
</template>
<script setup>
import { Multiselect } from "vue-multiselect";
import {
PayPalIcon,
SearchIcon,
RadioButtonIcon,
RadioButtonCheckedIcon,
XIcon,
RadioButtonIcon,
SearchIcon,
TransferIcon,
XIcon,
} from "@modrinth/assets";
import { Chips, Checkbox, Breadcrumbs } from "@modrinth/ui";
import { all } from "iso-3166-1";
import { Breadcrumbs, Checkbox, Chips, injectNotificationManager } from "@modrinth/ui";
import { formatMoney, formatWallet } from "@modrinth/utils";
import { all } from "iso-3166-1";
import { Multiselect } from "vue-multiselect";
import VenmoIcon from "~/assets/images/external/venmo.svg?component";
const { addNotification } = injectNotificationManager();
const auth = await useAuth();
const data = useNuxtApp();
@@ -355,8 +356,7 @@ async function withdraw() {
});
await useAuth(auth.value.token);
await navigateTo("/dashboard/revenue");
data.$notify({
group: "main",
addNotification({
title: "Withdrawal complete",
text:
selectedMethod.value.type === "tremendous"
@@ -365,8 +365,7 @@ async function withdraw() {
type: "success",
});
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -28,7 +28,7 @@
</nuxt-link>
</h2>
<span>
{{ $formatNumber(acceptedMembers?.length || 0) }}
{{ formatNumber(acceptedMembers?.length || 0) }}
member<template v-if="acceptedMembers?.length !== 1">s</template>
</span>
</div>
@@ -120,11 +120,11 @@
{
id: 'manage-projects',
action: () =>
navigateTo('/organization/' + organization.slug + '/settings/projects'),
hoverOnly: true,
shown: auth.user && currentMember,
router.push('/organization/' + organization?.slug + '/settings/projects'),
hoverFilledOnly: true,
shown: !!(auth.user && currentMember),
},
{ divider: true, shown: auth.user && currentMember },
{ divider: true, shown: !!(auth?.user && currentMember) },
{ id: 'copy-id', action: () => copyId() },
{ id: 'copy-permalink', action: () => copyPermalink() },
]"
@@ -157,20 +157,20 @@
<template v-for="member in acceptedMembers" :key="member.user.id">
<nuxt-link
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">
<span class="flex items-center gap-1">
{{ member.user.username }}
{{ member?.user?.username }}
<CrownIcon
v-if="member.is_owner"
v-if="member?.is_owner"
v-tooltip="'Organization owner'"
class="text-brand-orange"
/>
</span>
<span class="details-list__item__text--style-secondary">
{{ member.role ? member.role : "Member" }}
{{ member?.role ? member.role : "Member" }}
</span>
</div>
</nuxt-link>
@@ -196,16 +196,21 @@
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
<NavTabs :links="navLinks" />
</div>
<template v-if="projects?.length > 0">
<template v-if="projects && projects.length > 0">
<div class="project-list display-mode--list">
<ProjectCard
v-for="project in (route.params.projectType !== undefined
? projects.filter((x) =>
? (projects ?? []).filter((x) =>
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()
.sort((a, b) => b.downloads - a.downloads)"
@@ -225,9 +230,10 @@
:client-side="project.client_side"
:server-side="project.server_side"
:status="
auth.user && (auth.user.id === user.id || tags.staffRoles.includes(auth.user.role))
? project.status
: null
auth.user &&
(auth.user.id! === (user as any).id || tags.staffRoles.includes(auth.user.role))
? (project.status as ProjectStatus)
: undefined
"
:type="project.project_types[0] ?? 'project'"
:color="project.color"
@@ -240,9 +246,9 @@
<br />
<span class="preserve-lines text">
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
<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>
</span>
</div>
@@ -251,50 +257,58 @@
</div>
</template>
<script setup>
<script setup lang="ts">
import {
BoxIcon,
MoreVerticalIcon,
UsersIcon,
SettingsIcon,
ChartIcon,
CheckIcon,
XIcon,
ClipboardCopyIcon,
OrganizationIcon,
DownloadIcon,
CrownIcon,
DownloadIcon,
MoreVerticalIcon,
OrganizationIcon,
SettingsIcon,
UsersIcon,
XIcon,
} from "@modrinth/assets";
import {
Avatar,
ButtonStyled,
Breadcrumbs,
ButtonStyled,
commonMessages,
ContentPageHeader,
OverflowMenu,
commonMessages,
} 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 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 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 { formatMessage } = vintl;
const formatCompactNumber = useCompactNumber(true);
const auth = await useAuth();
const auth: { user: any } & any = await useAuth();
const user = await useUser();
const cosmetics = useCosmetics();
const route = useNativeRoute();
const router = useRouter();
const tags = useTags();
const config = useRuntimeConfig();
let orgId = useRouteId();
const orgId = useRouteId();
// hacky way to show the edit button on the corner of the card.
const routeHasSettings = computed(() => route.path.includes("settings"));
@@ -303,12 +317,13 @@ const [
{ data: organization, refresh: refreshOrganization },
{ data: projects, refresh: refreshProjects },
] = await Promise.all([
useAsyncData(`organization/${orgId}`, () =>
useBaseFetch(`organization/${orgId}`, { apiVersion: 3 }),
useAsyncData(
`organization/${orgId}`,
() => useBaseFetch(`organization/${orgId}`, { apiVersion: 3 }) as Promise<Organization>,
),
useAsyncData(
`organization/${orgId}/projects`,
() => useBaseFetch(`organization/${orgId}/projects`, { apiVersion: 3 }),
() => useBaseFetch(`organization/${orgId}/projects`, { apiVersion: 3 }) as Promise<ProjectV3[]>,
{
transform: (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
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 rest = acceptedMembers.filter((x) => !x.is_owner) || [];
@@ -374,43 +389,14 @@ const acceptedMembers = computed(() => {
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(() => {
return currentMember.value?.accepted === false;
});
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;
}
@@ -421,62 +407,27 @@ const projectTypes = computed(() => {
const sumDownloads = computed(() => {
let sum = 0;
for (const project of projects.value) {
for (const project of projects.value ?? []) {
sum += project.downloads;
}
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 () => {
await acceptTeamInvite(organization.value.team_id);
await acceptTeamInvite(organization.value?.team_id);
await refreshOrganization();
});
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();
});
provide("organizationContext", {
organization,
projects,
refresh,
currentMember,
hasPermission,
patchIcon,
deleteIcon,
patchOrganization,
});
const organizationContext = new OrganizationContext(organization, projects, auth, tags, refresh);
const { currentMember } = organizationContext;
provideOrganizationContext(organizationContext);
const title = `${organization.value.name} - Organization`;
const description = `${organization.value.description} - View the organization ${organization.value.name} on Modrinth`;
@@ -492,13 +443,13 @@ useSeoMeta({
const navLinks = computed(() => [
{
label: formatMessage(commonMessages.allProjectType),
href: `/organization/${organization.value.slug}`,
href: `/organization/${organization.value?.slug}`,
},
...projectTypes.value
.map((x) => {
return {
label: formatMessage(getProjectTypeMessage(x, true)),
href: `/organization/${organization.value.slug}/${x}s`,
label: formatMessage(getProjectTypeMessage(x as ProjectType, true)),
href: `/organization/${organization.value?.slug}/${x}s`,
};
})
.slice()
@@ -506,12 +457,12 @@ const navLinks = computed(() => [
]);
async function copyId() {
await navigator.clipboard.writeText(organization.value.id);
await navigator.clipboard.writeText(organization.value?.id ?? "");
}
async function copyPermalink() {
await navigator.clipboard.writeText(
`${config.public.siteUrl}/organization/${organization.value.id}`,
`${config.public.siteUrl}/organization/${organization.value?.id}`,
);
}
</script>

View File

@@ -16,8 +16,9 @@
<script setup>
import ChartDisplay from "~/components/ui/charts/ChartDisplay.vue";
import { injectOrganizationContext } from "~/providers/organization-context.ts";
const { projects } = inject("organizationContext");
const { projects } = injectOrganizationContext();
</script>
<style scoped lang="scss">

View File

@@ -1,7 +1,9 @@
<script setup>
import { Button, FileInput, Avatar, ConfirmModal } from "@modrinth/ui";
import { UploadIcon, SaveIcon, TrashIcon } from "@modrinth/assets";
import { SaveIcon, TrashIcon, UploadIcon } from "@modrinth/assets";
import { Avatar, Button, ConfirmModal, FileInput, injectNotificationManager } from "@modrinth/ui";
import { injectOrganizationContext } from "~/providers/organization-context.ts";
const { addNotification } = injectNotificationManager();
const {
organization,
refresh: refreshOrganization,
@@ -9,7 +11,7 @@ const {
deleteIcon,
patchIcon,
patchOrganization,
} = inject("organizationContext");
} = injectOrganizationContext();
const icon = ref(null);
const deletedIcon = ref(false);
@@ -74,7 +76,6 @@ const onSaveChanges = useClientTry(async () => {
await refreshOrganization();
addNotification({
group: "main",
title: "Organization updated",
text: "Your organization has been updated.",
type: "success",
@@ -88,7 +89,6 @@ const onDeleteOrganization = useClientTry(async () => {
});
addNotification({
group: "main",
title: "Organization deleted",
text: "Your organization has been deleted.",
type: "success",

View File

@@ -220,19 +220,21 @@
<script setup>
import {
CrownIcon,
DropdownIcon,
SaveIcon,
TransferIcon,
UserPlusIcon,
UserXIcon as UserRemoveIcon,
DropdownIcon,
CrownIcon,
} from "@modrinth/assets";
import { Button, Badge, Avatar, Checkbox } from "@modrinth/ui";
import { Avatar, Badge, Button, Checkbox, injectNotificationManager } from "@modrinth/ui";
import { ref } from "vue";
import { removeTeamMember } from "~/helpers/teams.js";
import { injectOrganizationContext } from "~/providers/organization-context.ts";
import { isPermission } from "~/utils/permissions.ts";
const { organization, refresh: refreshOrganization, currentMember } = inject("organizationContext");
const { addNotification } = injectNotificationManager();
const { organization, refresh: refreshOrganization, currentMember } = injectOrganizationContext();
const auth = await useAuth();
@@ -296,7 +298,6 @@ const onInviteTeamMember = useClientTry(async (teamId, username) => {
await refreshOrganization();
currentUsername.value = "";
addNotification({
group: "main",
title: "Member invited",
text: `${user.username} has been invited to the organization.`,
type: "success",
@@ -307,7 +308,6 @@ const onRemoveMember = useClientTry(async (teamId, member) => {
await removeTeamMember(teamId, member.user.id);
await refreshOrganization();
addNotification({
group: "main",
title: "Member removed",
text: `${member.user.username} has been removed from the organization.`,
type: "success",
@@ -332,7 +332,6 @@ const onUpdateTeamMember = useClientTry(async (teamId, member) => {
});
await refreshOrganization();
addNotification({
group: "main",
title: "Member updated",
text: `${member.user.username} has been updated.`,
type: "success",
@@ -349,7 +348,6 @@ const onTransferOwnership = useClientTry(async (teamId, uid) => {
});
await refreshOrganization();
addNotification({
group: "main",
title: "Ownership transferred",
text: `The ownership of ${organization.value.name} has been successfully transferred.`,
type: "success",

View File

@@ -298,28 +298,38 @@
</template>
<script setup>
import { Multiselect } from "vue-multiselect";
import {
BoxIcon,
SettingsIcon,
TrashIcon,
EditIcon,
IssuesIcon,
PlusIcon,
XIcon,
EditIcon,
SaveIcon,
SettingsIcon,
SortAscIcon,
SortDescIcon,
TrashIcon,
XIcon,
} from "@modrinth/assets";
import { Button, Modal, Avatar, CopyCode, Badge, Checkbox, commonMessages } from "@modrinth/ui";
import {
Avatar,
Badge,
Button,
Checkbox,
commonMessages,
CopyCode,
injectNotificationManager,
Modal,
} from "@modrinth/ui";
import { formatProjectType } from "@modrinth/utils";
import { Multiselect } from "vue-multiselect";
import ModalCreation from "~/components/ui/ModalCreation.vue";
import OrganizationProjectTransferModal from "~/components/ui/OrganizationProjectTransferModal.vue";
import { injectOrganizationContext } from "~/providers/organization-context.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const { organization, projects, refresh } = inject("organizationContext");
const { organization, projects, refresh } = injectOrganizationContext();
const auth = await useAuth();
@@ -375,14 +385,12 @@ const onProjectTransferSubmit = async (projects) => {
await refreshUserProjects();
addNotification({
group: "main",
title: "Success",
text: "Transferred selected projects to organization.",
type: "success",
});
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err?.data?.description || err?.message || err || "Unknown error",
type: "error",
@@ -511,7 +519,6 @@ const onBulkEditLinks = useClientTry(async () => {
editLinksModal.value.hide();
addNotification({
group: "main",
title: "Success",
text: "Bulk edited selected project's links.",
type: "success",

View File

@@ -11,8 +11,7 @@
:fetch-payment-data="fetchPaymentData"
:on-error="
(err) =>
data.$notify({
group: 'main',
addNotification({
title: 'An error occurred',
type: 'error',
text: err.message ?? (err.data ? err.data.description : err),
@@ -87,16 +86,18 @@
</template>
<script setup>
import {
ModrinthPlusIcon,
HeartIcon,
ModrinthPlusIcon,
SettingsIcon,
SparklesIcon,
StarIcon,
SettingsIcon,
} from "@modrinth/assets";
import { PurchaseModal } from "@modrinth/ui";
import { injectNotificationManager, PurchaseModal } from "@modrinth/ui";
import { calculateSavings, formatPrice, getCurrency } from "@modrinth/utils";
import { products } from "~/generated/state.json";
const { addNotification } = injectNotificationManager();
const title = "Subscribe to Modrinth Plus!";
const description =
"Subscribe to Modrinth Plus to go ad-free, support Modrinth's development, and get an exclusive profile badge! Half your subscription goes directly to Modrinth creators.";
@@ -120,7 +121,6 @@ useHead({
const vintl = useVIntl();
const data = useNuxtApp();
const config = useRuntimeConfig();
const auth = await useAuth();

View File

@@ -243,30 +243,33 @@
<script setup lang="ts">
import {
CheckCircleIcon,
CheckIcon,
ExternalIcon,
IssuesIcon,
LeftArrowIcon,
RightArrowIcon,
ScaleIcon,
SendIcon,
SpinnerIcon,
VersionIcon,
XCircleIcon,
} from "@modrinth/assets";
import {
AutoLink,
Avatar,
ButtonStyled,
injectNotificationManager,
MarkdownEditor,
RadialHeader,
RadioButtons,
ButtonStyled,
Avatar,
AutoLink,
} from "@modrinth/ui";
import {
ExternalIcon,
LeftArrowIcon,
RightArrowIcon,
CheckIcon,
SpinnerIcon,
SendIcon,
IssuesIcon,
CheckCircleIcon,
XCircleIcon,
ScaleIcon,
VersionIcon,
} from "@modrinth/assets";
import type { User, Version, Report } from "@modrinth/utils";
import { useVIntl, defineMessages, type MessageDescriptor } from "@vintl/vintl";
import type { Project, Report, User, Version } from "@modrinth/utils";
import { defineMessages, useVIntl, type MessageDescriptor } from "@vintl/vintl";
import { useImageUpload } from "~/composables/image-upload.ts";
const { addNotification } = injectNotificationManager();
const tags = useTags();
const route = useNativeRoute();
const router = useRouter();
@@ -439,7 +442,6 @@ const submitReport = async () => {
if (error instanceof Error) {
addNotification({
group: "main",
title: "An error occurred",
text: error.message,
type: "error",
@@ -465,7 +467,6 @@ const submitReport = async () => {
if (error instanceof Error) {
addNotification({
group: "main",
title: "An error occurred",
text: error.message,
type: "error",

View File

@@ -632,26 +632,31 @@
</template>
<script setup>
import { ButtonStyled, ModrinthServersPurchaseModal } from "@modrinth/ui";
import {
BoxIcon,
GameIcon,
RightArrowIcon,
ServerIcon,
TerminalSquareIcon,
TransferIcon,
VersionIcon,
ServerIcon,
} from "@modrinth/assets";
import { computed } from "vue";
import {
ButtonStyled,
injectNotificationManager,
ModrinthServersPurchaseModal,
} from "@modrinth/ui";
import { monthsInInterval } from "@modrinth/ui/src/utils/billing.ts";
import { formatPrice } from "@modrinth/utils";
import { useVIntl } from "@vintl/vintl";
import { products } from "~/generated/state.json";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { computed } from "vue";
import OptionGroup from "~/components/ui/OptionGroup.vue";
import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue";
import ServerPlanSelector from "~/components/ui/servers/marketing/ServerPlanSelector.vue";
import OptionGroup from "~/components/ui/OptionGroup.vue";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { products } from "~/generated/state.json";
const { addNotification } = injectNotificationManager();
const { locale } = useVIntl();
const billingPeriods = ref(["monthly", "quarterly"]);
@@ -812,7 +817,6 @@ const startTyping = () => {
const handleError = (err) => {
addNotification({
group: "main",
title: "An error occurred",
type: "error",
text: err.message ?? (err.data ? err.data.description : err),
@@ -831,7 +835,6 @@ async function fetchPaymentData() {
} catch (error) {
console.error("Error fetching payment data:", error);
addNotification({
group: "main",
title: "Error fetching payment data",
type: "error",
text: error.message || "An unexpected error occurred",
@@ -886,7 +889,6 @@ const selectProduct = async (product) => {
if ((product === "custom" && isCustomAtCapacity.value) || isAtCapacity.value) {
addNotification({
group: "main",
title: "Server Capacity Full",
type: "error",
text: "We are currently at capacity. Please try again later.",
@@ -902,7 +904,6 @@ const selectProduct = async (product) => {
(product !== "custom" && !selectedPlan.metadata)
) {
addNotification({
group: "main",
title: "Invalid product",
type: "error",
text: "The selected product was found but lacks necessary data. Please contact support.",

View File

@@ -350,38 +350,43 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, type Reactive } from "vue";
import { reloadNuxtApp } from "#app";
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
import {
SettingsIcon,
CheckIcon,
CopyIcon,
FileIcon,
IssuesIcon,
LeftArrowIcon,
RightArrowIcon,
CheckIcon,
FileIcon,
TransferIcon,
LockIcon,
RightArrowIcon,
SettingsIcon,
TransferIcon,
} from "@modrinth/assets";
import DOMPurify from "dompurify";
import { ButtonStyled, ErrorInformationCard, ServerNotice } from "@modrinth/ui";
import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
import type { MessageDescriptor } from "@vintl/vintl";
import {
ButtonStyled,
ErrorInformationCard,
injectNotificationManager,
ServerNotice,
} from "@modrinth/ui";
import {
type Backup,
type PowerAction,
type ServerState,
type Stats,
type WSEvent,
type WSInstallationResultEvent,
type Backup,
type PowerAction,
} from "@modrinth/utils";
import { reloadNuxtApp } from "#app";
import { useModrinthServersConsole } from "~/store/console.ts";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
import type { MessageDescriptor } from "@vintl/vintl";
import DOMPurify from "dompurify";
import { computed, onMounted, onUnmounted, ref, type Reactive } from "vue";
import ServerInstallation from "~/components/ui/servers/ServerInstallation.vue";
import PanelErrorIcon from "~/components/ui/servers/icons/PanelErrorIcon.vue";
import { ModrinthServer, useModrinthServers } from "~/composables/servers/modrinth-servers.ts";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { useModrinthServersConsole } from "~/store/console.ts";
const app = useNuxtApp() as unknown as { $notify: any };
const { addNotification } = injectNotificationManager();
const socket = ref<WebSocket | null>(null);
const isReconnecting = ref(false);
@@ -964,7 +969,6 @@ const sendPowerAction = async (action: PowerAction) => {
const notifyError = (title: string, text: string) => {
addNotification({
group: "server",
title,
text,
type: "error",
@@ -1149,8 +1153,7 @@ async function dismissNotice(noticeId: number) {
await useServersFetch(`servers/${serverId}/notices/${noticeId}/dismiss`, {
method: "POST",
}).catch((err) => {
app.$notify({
group: "main",
addNotification({
title: "Error dismissing notice",
text: err,
type: "error",

View File

@@ -150,19 +150,20 @@
</template>
<script setup lang="ts">
import { ButtonStyled, TagItem } from "@modrinth/ui";
import { useStorage } from "@vueuse/core";
import { SpinnerIcon, PlusIcon, DownloadIcon, SettingsIcon, IssuesIcon } from "@modrinth/assets";
import { ref, computed } from "vue";
import { DownloadIcon, IssuesIcon, PlusIcon, SettingsIcon, SpinnerIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager, TagItem } from "@modrinth/ui";
import type { Backup } from "@modrinth/utils";
import { useStorage } from "@vueuse/core";
import { computed, ref } from "vue";
import BackupCreateModal from "~/components/ui/servers/BackupCreateModal.vue";
import BackupDeleteModal from "~/components/ui/servers/BackupDeleteModal.vue";
import BackupItem from "~/components/ui/servers/BackupItem.vue";
import BackupRenameModal from "~/components/ui/servers/BackupRenameModal.vue";
import BackupCreateModal from "~/components/ui/servers/BackupCreateModal.vue";
import BackupRestoreModal from "~/components/ui/servers/BackupRestoreModal.vue";
import BackupDeleteModal from "~/components/ui/servers/BackupDeleteModal.vue";
import BackupSettingsModal from "~/components/ui/servers/BackupSettingsModal.vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
isServerRunning: boolean;
@@ -236,7 +237,11 @@ const prepareDownload = async (backupId: string) => {
await props.server.backups?.prepare(backupId);
} catch (error) {
console.error("Failed to prepare download:", error);
addNotification({ type: "error", title: "Failed to prepare backup for download", text: error });
addNotification({
type: "error",
title: "Failed to prepare backup for download",
text: error as string,
});
}
};

View File

@@ -335,28 +335,29 @@
<script setup lang="ts">
import {
SearchIcon,
EditIcon,
TrashIcon,
PackageClosedIcon,
FilterIcon,
DropdownIcon,
PlusIcon,
MoreVerticalIcon,
CompassIcon,
WrenchIcon,
ListIcon,
DropdownIcon,
EditIcon,
FileIcon,
FilterIcon,
IssuesIcon,
ListIcon,
MoreVerticalIcon,
PackageClosedIcon,
PlusIcon,
SearchIcon,
TrashIcon,
WrenchIcon,
} from "@modrinth/assets";
import { Avatar, ButtonStyled } from "@modrinth/ui";
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
import { Avatar, ButtonStyled, injectNotificationManager } from "@modrinth/ui";
import type { Mod } from "@modrinth/utils";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import FilesUploadDragAndDrop from "~/components/ui/servers/FilesUploadDragAndDrop.vue";
import FilesUploadDropdown from "~/components/ui/servers/FilesUploadDropdown.vue";
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
import { acceptFileFromProjectType } from "~/helpers/fileUtils.js";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();

View File

@@ -266,26 +266,27 @@
</template>
<script setup lang="ts">
import { useInfiniteScroll } from "@vueuse/core";
import {
UnknownIcon,
XIcon,
SpinnerIcon,
PackageOpenIcon,
CheckIcon,
UploadIcon,
FolderOpenIcon,
PackageOpenIcon,
SpinnerIcon,
UnknownIcon,
UploadIcon,
XIcon,
} from "@modrinth/assets";
import { computed } from "vue";
import { ButtonStyled, ProgressBar } from "@modrinth/ui";
import { ButtonStyled, injectNotificationManager, ProgressBar } from "@modrinth/ui";
import type { DirectoryItem, DirectoryResponse, FilesystemOp, FSQueuedOp } from "@modrinth/utils";
import { formatBytes, ModrinthServersFetchError } from "@modrinth/utils";
import type { FilesystemOp, FSQueuedOp, DirectoryItem, DirectoryResponse } from "@modrinth/utils";
import { handleError, ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
import { useInfiniteScroll } from "@vueuse/core";
import { computed } from "vue";
import FilesUploadConflictModal from "~/components/ui/servers/FilesUploadConflictModal.vue";
import FilesUploadDragAndDrop from "~/components/ui/servers/FilesUploadDragAndDrop.vue";
import FilesUploadDropdown from "~/components/ui/servers/FilesUploadDropdown.vue";
import FilesUploadZipUrlModal from "~/components/ui/servers/FilesUploadZipUrlModal.vue";
import FilesUploadConflictModal from "~/components/ui/servers/FilesUploadConflictModal.vue";
import { handleError, ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const flags = useFeatureFlags();
const baseId = useId();
@@ -449,7 +450,6 @@ const undoLastOperation = async () => {
refreshList();
addNotification({
group: "files",
title: `${lastOperation.type === "move" ? "Move" : "Rename"} undone`,
text: `${lastOperation.fileName} has been restored to its original ${lastOperation.type === "move" ? "location" : "name"}`,
type: "success",
@@ -457,7 +457,6 @@ const undoLastOperation = async () => {
} catch (error) {
console.error(`Error undoing ${lastOperation.type}:`, error);
addNotification({
group: "files",
title: "Undo failed",
text: `Failed to undo the last ${lastOperation.type} operation`,
type: "error",
@@ -489,7 +488,6 @@ const redoLastOperation = async () => {
refreshList();
addNotification({
group: "files",
title: `${lastOperation.type === "move" ? "Move" : "Rename"} redone`,
text: `${lastOperation.fileName} has been ${lastOperation.type === "move" ? "moved" : "renamed"} again`,
type: "success",
@@ -497,7 +495,6 @@ const redoLastOperation = async () => {
} catch (error) {
console.error(`Error redoing ${lastOperation.type}:`, error);
addNotification({
group: "files",
title: "Redo failed",
text: `Failed to redo the last ${lastOperation.type} operation`,
type: "error",
@@ -513,7 +510,6 @@ const handleCreateNewItem = async (name: string) => {
refreshList();
addNotification({
group: "files",
title: `${newItemType.value === "directory" ? "Folder" : "File"} created`,
text: `New ${newItemType.value === "directory" ? "folder" : "file"} ${name} has been created.`,
type: "success",
@@ -549,7 +545,6 @@ const handleRenameItem = async (newName: string) => {
}
addNotification({
group: "files",
title: `${selectedItem.value.type === "directory" ? "Folder" : "File"} renamed`,
text: `${selectedItem.value.name} has been renamed to ${newName}`,
type: "success",
@@ -559,7 +554,6 @@ const handleRenameItem = async (newName: string) => {
if (error instanceof ModrinthServersFetchError) {
if (error.statusCode === 400) {
addNotification({
group: "files",
title: "Could not rename",
text: `An item named "${newName}" already exists in this location`,
type: "error",
@@ -567,7 +561,6 @@ const handleRenameItem = async (newName: string) => {
return;
}
addNotification({
group: "files",
title: "Could not rename item",
text: "An unexpected error occurred",
type: "error",
@@ -625,7 +618,6 @@ const handleMoveItem = async (destination: string) => {
refreshList();
addNotification({
group: "files",
title: `${itemType === "directory" ? "Folder" : "File"} moved`,
text: `${selectedItem.value.name} has been moved to ${newPath}`,
type: "success",
@@ -658,7 +650,6 @@ const handleDirectMove = async (moveData: {
refreshList();
addNotification({
group: "files",
title: `${moveData.type === "directory" ? "Folder" : "File"} moved`,
text: `${moveData.name} has been moved to ${newPath}`,
type: "success",
@@ -675,7 +666,6 @@ const handleDeleteItem = async () => {
refreshList();
addNotification({
group: "files",
title: "File deleted",
text: "Your file has been deleted.",
type: "success",
@@ -717,14 +707,12 @@ const handleCreateError = (error: any) => {
if (error instanceof ModrinthServersFetchError) {
if (error.statusCode === 400) {
addNotification({
group: "files",
title: "Error creating item",
text: "Invalid file",
type: "error",
});
} else if (error.statusCode === 500) {
addNotification({
group: "files",
title: "Error creating item",
text: "Something went wrong. The file may already exist.",
type: "error",
@@ -1010,7 +998,6 @@ const requestShareLink = async () => {
if (response.success) {
await navigator.clipboard.writeText(response.url);
addNotification({
group: "files",
title: "Log URL copied",
text: "Your log file URL has been copied to your clipboard.",
type: "success",
@@ -1080,7 +1067,6 @@ const saveFileContent = async (exit: boolean = true) => {
}
addNotification({
group: "files",
title: "File saved",
text: "Your file has been saved.",
type: "success",
@@ -1095,7 +1081,6 @@ const saveFileContentRestart = async () => {
await props.server.general?.power("Restart");
addNotification({
group: "files",
title: "Server restarted",
text: "Your server has been restarted.",
type: "success",

View File

@@ -113,9 +113,11 @@
<script setup lang="ts">
import { EditIcon, TransferIcon } from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import ButtonStyled from "@modrinth/ui/src/components/base/ButtonStyled.vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -161,7 +163,6 @@ const saveGeneral = async () => {
if (!available) {
addNotification({
group: "serverOptions",
type: "error",
title: "Subdomain not available",
text: "The subdomain you entered is already in use.",
@@ -173,7 +174,6 @@ const saveGeneral = async () => {
} catch (error) {
console.error("Error checking subdomain availability:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Error checking availability",
text: "Failed to verify if the subdomain is available.",
@@ -184,7 +184,6 @@ const saveGeneral = async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await props.server.refresh();
addNotification({
group: "serverOptions",
type: "success",
title: "Server settings updated",
text: "Your server settings were successfully changed.",
@@ -192,7 +191,6 @@ const saveGeneral = async () => {
} catch (error) {
console.error(error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server settings",
text: "An error occurred while attempting to update your server settings.",
@@ -211,7 +209,6 @@ const uploadFile = async (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) {
addNotification({
group: "serverOptions",
type: "error",
title: "No file selected",
text: "Please select a file to upload.",
@@ -267,7 +264,6 @@ const uploadFile = async (e: Event) => {
});
addNotification({
group: "serverOptions",
type: "success",
title: "Server icon updated",
text: "Your server icon was successfully changed.",
@@ -275,7 +271,6 @@ const uploadFile = async (e: Event) => {
} catch (error) {
console.error("Error uploading icon:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Upload failed",
text: "Failed to upload server icon.",
@@ -295,7 +290,6 @@ const resetIcon = async () => {
await props.server.refresh(["general"]);
addNotification({
group: "serverOptions",
type: "success",
title: "Server icon reset",
text: "Your server icon was successfully reset.",
@@ -303,7 +297,6 @@ const resetIcon = async () => {
} catch (error) {
console.error("Error resetting icon:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Reset failed",
text: "Failed to reset server icon.",

View File

@@ -115,10 +115,11 @@
</template>
<script setup lang="ts">
import { ButtonStyled, CopyCode } from "@modrinth/ui";
import { CopyIcon, ExternalIcon, EyeIcon, EyeOffIcon } from "@modrinth/assets";
import { ButtonStyled, CopyCode, injectNotificationManager } from "@modrinth/ui";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();

View File

@@ -264,19 +264,26 @@
<script setup lang="ts">
import {
PlusIcon,
TrashIcon,
EditIcon,
VersionIcon,
SaveIcon,
InfoIcon,
UploadIcon,
IssuesIcon,
PlusIcon,
SaveIcon,
TrashIcon,
UploadIcon,
VersionIcon,
} from "@modrinth/assets";
import { ButtonStyled, NewModal, ConfirmModal, CopyCode } from "@modrinth/ui";
import { ref, computed, nextTick } from "vue";
import {
ButtonStyled,
ConfirmModal,
CopyCode,
injectNotificationManager,
NewModal,
} from "@modrinth/ui";
import { computed, nextTick, ref } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -317,7 +324,6 @@ const addNewAllocation = async () => {
newAllocationName.value = "";
addNotification({
group: "serverOptions",
type: "success",
title: "Allocation reserved",
text: "Your allocation has been reserved.",
@@ -359,7 +365,6 @@ const confirmDeleteAllocation = async () => {
await props.server.refresh(["network"]);
addNotification({
group: "serverOptions",
type: "success",
title: "Allocation removed",
text: "Your allocation has been removed.",
@@ -379,7 +384,6 @@ const editAllocation = async () => {
newAllocationName.value = "";
addNotification({
group: "serverOptions",
type: "success",
title: "Allocation updated",
text: "Your allocation has been updated.",
@@ -397,7 +401,6 @@ const saveNetwork = async () => {
const available = await props.server.network?.checkSubdomainAvailability(serverSubdomain.value);
if (!available) {
addNotification({
group: "serverOptions",
type: "error",
title: "Subdomain not available",
text: "The subdomain you entered is already in use.",
@@ -416,7 +419,6 @@ const saveNetwork = async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await props.server.refresh();
addNotification({
group: "serverOptions",
type: "success",
title: "Server settings updated",
text: "Your server settings were successfully changed.",
@@ -424,7 +426,6 @@ const saveNetwork = async () => {
} catch (error) {
console.error(error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server settings",
text: "An error occurred while attempting to update your server settings.",
@@ -483,7 +484,6 @@ const exportDnsRecords = () => {
const copyText = (text: string) => {
navigator.clipboard.writeText(text);
addNotification({
group: "serverOptions",
type: "success",
title: "Text copied",
text: `${text} has been copied to your clipboard`,

View File

@@ -42,9 +42,11 @@
</template>
<script setup lang="ts">
import { injectNotificationManager } from "@modrinth/ui";
import { useStorage } from "@vueuse/core";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const route = useNativeRoute();
const serverId = route.params.id as string;
@@ -109,7 +111,6 @@ const hasUnsavedChanges = computed(() => {
const savePreferences = () => {
userPreferences.value = { ...newUserPreferences.value };
addNotification({
group: "serverOptions",
type: "success",
title: "Preferences saved",
text: "Your preferences have been saved.",

View File

@@ -143,11 +143,13 @@
</template>
<script setup lang="ts">
import { ref, watch, computed, inject } from "vue";
import { EyeIcon, SearchIcon, IssuesIcon } from "@modrinth/assets";
import { EyeIcon, IssuesIcon, SearchIcon } from "@modrinth/assets";
import { injectNotificationManager } from "@modrinth/ui";
import Fuse from "fuse.js";
import { computed, inject, ref, watch } from "vue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -281,7 +283,6 @@ const saveProperties = async () => {
originalProperties.value = JSON.parse(JSON.stringify(liveProperties.value));
await props.server.refresh();
addNotification({
group: "serverOptions",
type: "success",
title: "Server properties updated",
text: "Your server properties were successfully changed.",
@@ -289,7 +290,6 @@ const saveProperties = async () => {
} catch (error) {
console.error("Error updating server properties:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server properties",
text: "An error occurred while attempting to update your server properties.",

View File

@@ -112,10 +112,11 @@
</template>
<script setup lang="ts">
import { UpdatedIcon, IssuesIcon } from "@modrinth/assets";
import { ButtonStyled } from "@modrinth/ui";
import { IssuesIcon, UpdatedIcon } from "@modrinth/assets";
import { ButtonStyled, injectNotificationManager } from "@modrinth/ui";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const { addNotification } = injectNotificationManager();
const props = defineProps<{
server: ModrinthServer;
}>();
@@ -199,7 +200,6 @@ async function saveStartup() {
}
addNotification({
group: "serverOptions",
type: "success",
title: "Server settings updated",
text: "Your server settings were successfully changed.",
@@ -207,7 +207,6 @@ async function saveStartup() {
} catch (error) {
console.error(error);
addNotification({
group: "serverOptions",
type: "error",
title: "Failed to update server arguments",
text: "Please try again later.",

View File

@@ -408,6 +408,7 @@
<script setup>
import {
CheckIcon,
DownloadIcon,
EditIcon,
ExternalIcon,
LeftArrowIcon,
@@ -418,17 +419,16 @@ import {
TrashIcon,
UpdatedIcon,
XIcon,
DownloadIcon,
} from "@modrinth/assets";
import { ConfirmModal, injectNotificationManager } from "@modrinth/ui";
import QrcodeVue from "qrcode.vue";
import { ConfirmModal } from "@modrinth/ui";
import GithubIcon from "assets/icons/auth/sso-github.svg";
import MicrosoftIcon from "assets/icons/auth/sso-microsoft.svg";
import GoogleIcon from "assets/icons/auth/sso-google.svg";
import SteamIcon from "assets/icons/auth/sso-steam.svg";
import DiscordIcon from "assets/icons/auth/sso-discord.svg";
import KeyIcon from "assets/icons/auth/key.svg";
import DiscordIcon from "assets/icons/auth/sso-discord.svg";
import GithubIcon from "assets/icons/auth/sso-github.svg";
import GitLabIcon from "assets/icons/auth/sso-gitlab.svg";
import GoogleIcon from "assets/icons/auth/sso-google.svg";
import MicrosoftIcon from "assets/icons/auth/sso-microsoft.svg";
import SteamIcon from "assets/icons/auth/sso-steam.svg";
import Modal from "~/components/ui/Modal.vue";
useHead({
@@ -439,7 +439,7 @@ definePageMeta({
middleware: "auth",
});
const data = useNuxtApp();
const { addNotification } = injectNotificationManager();
const auth = await useAuth();
const changeEmailModal = ref();
@@ -460,8 +460,7 @@ async function saveEmail() {
changeEmailModal.value.hide();
await useAuth(auth.value.token);
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -492,8 +491,7 @@ async function savePassword() {
managePasswordModal.value.hide();
await useAuth(auth.value.token);
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -529,8 +527,7 @@ async function showTwoFactorModal() {
twoFactorSecret.value = res.secret;
twoFactorFlow.value = res.flow;
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -619,8 +616,7 @@ async function deleteAccount() {
method: "DELETE",
});
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -649,8 +645,7 @@ async function exportData() {
const blob = new Blob([jsonString], { type: "application/json" });
generated.value = URL.createObjectURL(blob);
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -215,26 +215,27 @@
</div>
</template>
<script setup>
import { UploadIcon, PlusIcon, XIcon, TrashIcon, EditIcon, SaveIcon } from "@modrinth/assets";
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, UploadIcon, XIcon } from "@modrinth/assets";
import {
CopyCode,
ConfirmModal,
Avatar,
Button,
Checkbox,
Avatar,
ConfirmModal,
CopyCode,
FileInput,
commonSettingsMessages,
injectNotificationManager,
} from "@modrinth/ui";
import Modal from "~/components/ui/Modal.vue";
import {
scopeList,
getScopeValue,
hasScope,
scopeList,
toggleScope,
useScopes,
getScopeValue,
} from "~/composables/auth/scopes.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
definePageMeta({
@@ -245,7 +246,6 @@ useHead({
title: "Applications - Modrinth",
});
const data = useNuxtApp();
const { scopesToLabels } = useScopes();
const appModal = ref();
@@ -343,8 +343,7 @@ async function onImageSelection(files) {
setForm(app);
}
data.$notify({
group: "main",
addNotification({
title: "Icon updated",
text: "Your application icon has been updated.",
type: "success",
@@ -374,8 +373,7 @@ async function createApp() {
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -443,8 +441,7 @@ async function editApp() {
appModal.value.hide();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -467,8 +464,7 @@ async function removeApp() {
await refresh();
editingId.value = null;
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -88,10 +88,17 @@
</div>
</template>
<script setup>
import { Button, ConfirmModal, Avatar, commonSettingsMessages } from "@modrinth/ui";
import { TrashIcon, CheckIcon } from "@modrinth/assets";
import { CheckIcon, TrashIcon } from "@modrinth/assets";
import {
Avatar,
Button,
commonSettingsMessages,
ConfirmModal,
injectNotificationManager,
} from "@modrinth/ui";
import { useScopes } from "~/composables/auth/scopes.ts";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const { scopesToDefinitions } = useScopes();
@@ -163,8 +170,7 @@ async function revokeApp(id) {
revokingId.value = null;
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -392,8 +392,7 @@
"
:on-error="
(err) =>
data.$notify({
group: 'main',
addNotification({
title: 'An error occurred',
type: 'error',
text: err.message ?? (err.data ? err.data.description : err),
@@ -422,8 +421,7 @@
:renewal-date="currentSubRenewalDate"
:on-error="
(err) =>
data.$notify({
group: 'main',
addNotification({
title: 'An error occurred',
type: 'error',
text: err.message ?? (err.data ? err.data.description : err),
@@ -554,43 +552,44 @@
<script setup>
import {
ConfirmModal,
ArrowBigUpDashIcon,
CardIcon,
CheckCircleIcon,
CurrencyIcon,
EditIcon,
HistoryIcon,
ModrinthPlusIcon,
MoreVerticalIcon,
PayPalIcon,
PlusIcon,
RightArrowIcon,
SpinnerIcon,
StarIcon,
TransferIcon,
TrashIcon,
UpdatedIcon,
XIcon,
} from "@modrinth/assets";
import {
AddPaymentMethodModal,
ButtonStyled,
ConfirmModal,
CopyCode,
OverflowMenu,
PurchaseModal,
ButtonStyled,
CopyCode,
commonMessages,
injectNotificationManager,
} from "@modrinth/ui";
import {
PlusIcon,
TransferIcon,
SpinnerIcon,
ArrowBigUpDashIcon,
XIcon,
CardIcon,
MoreVerticalIcon,
TrashIcon,
EditIcon,
StarIcon,
PayPalIcon,
CurrencyIcon,
CheckCircleIcon,
RightArrowIcon,
ModrinthPlusIcon,
UpdatedIcon,
HistoryIcon,
} from "@modrinth/assets";
import { calculateSavings, formatPrice, getCurrency } from "@modrinth/utils";
import { ref, computed } from "vue";
import { computed, ref } from "vue";
import { useServersFetch } from "~/composables/servers/servers-fetch.ts";
import { products } from "~/generated/state.json";
const { addNotification } = injectNotificationManager();
definePageMeta({
middleware: "auth",
});
const app = useNuxtApp();
const auth = await useAuth();
const baseId = useId();
@@ -604,7 +603,6 @@ useHead({
],
});
const data = useNuxtApp();
const config = useRuntimeConfig();
const vintl = useVIntl();
@@ -845,8 +843,7 @@ async function editPaymentMethod(index, primary) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -864,8 +861,7 @@ async function removePaymentMethod(index) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -887,8 +883,7 @@ async function cancelSubscription(id, cancelled) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
@@ -955,8 +950,7 @@ const showPyroUpgradeModal = async (subscription) => {
if (!currentProduct.value) {
console.error("Could not find product for current subscription");
data.$notify({
group: "main",
addNotification({
title: "An error occurred",
text: "Could not find product for current subscription",
type: "error",
@@ -988,8 +982,7 @@ async function fetchCapacityStatuses(serverId, product) {
};
} catch (error) {
console.error("Error checking server capacities:", error);
app.$notify({
group: "main",
addNotification({
title: "Error checking server capacities",
text: error,
type: "error",
@@ -1015,23 +1008,20 @@ const resubscribePyro = async (subscriptionId, wasSuspended) => {
});
await refresh();
if (wasSuspended) {
data.$notify({
group: "main",
addNotification({
title: "Resubscription request submitted",
text: "If the server is currently suspended, it may take up to 10 minutes for another charge attempt to be made.",
type: "success",
});
} else {
data.$notify({
group: "main",
addNotification({
title: "Success",
text: "Server subscription resubscribed successfully",
type: "success",
});
}
} catch {
data.$notify({
group: "main",
addNotification({
title: "Error resubscribing",
text: "An error occurred while resubscribing to your Modrinth server.",
type: "error",

View File

@@ -199,7 +199,7 @@
<script setup lang="ts">
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from "@modrinth/assets";
import { Button, ThemeSelector } from "@modrinth/ui";
import { Button, injectNotificationManager, ThemeSelector } from "@modrinth/ui";
import { formatProjectType } from "@modrinth/utils";
import MessageBanner from "~/components/ui/MessageBanner.vue";
import type { DisplayLocation } from "~/plugins/cosmetics";
@@ -209,6 +209,7 @@ useHead({
title: "Display settings - Modrinth",
});
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const developerModeBanner = defineMessages({
@@ -377,7 +378,6 @@ function disableDeveloperMode() {
flags.value.developerMode = !flags.value.developerMode;
saveFeatureFlags();
addNotification({
group: "main",
title: "Developer mode deactivated",
text: "Developer mode has been disabled",
type: "success",

View File

@@ -202,26 +202,26 @@
</div>
</template>
<script setup>
import { PlusIcon, XIcon, TrashIcon, EditIcon, SaveIcon } from "@modrinth/assets";
import { EditIcon, PlusIcon, SaveIcon, TrashIcon, XIcon } from "@modrinth/assets";
import {
Checkbox,
CopyCode,
ConfirmModal,
commonSettingsMessages,
CopyCode,
commonMessages,
commonSettingsMessages,
injectNotificationManager,
useRelativeTime,
} from "@modrinth/ui";
import Modal from "~/components/ui/Modal.vue";
import {
getScopeValue,
hasScope,
scopeList,
toggleScope,
useScopes,
getScopeValue,
} from "~/composables/auth/scopes.ts";
import Modal from "~/components/ui/Modal.vue";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const formatRelativeTime = useRelativeTime();
@@ -346,8 +346,7 @@ async function createPat() {
pats.value.push(res);
patModal.value.hide();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -372,8 +371,7 @@ async function editPat() {
await refresh();
patModal.value.hide();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",
@@ -392,8 +390,7 @@ async function removePat(id) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -90,8 +90,11 @@
</template>
<script setup>
import { UserIcon, SaveIcon, UploadIcon, UndoIcon, XIcon, TrashIcon } from "@modrinth/assets";
import { Avatar, FileInput, Button, commonMessages } from "@modrinth/ui";
import { SaveIcon, TrashIcon, UndoIcon, UploadIcon, UserIcon, XIcon } from "@modrinth/assets";
import { Avatar, Button, commonMessages, FileInput, injectNotificationManager } from "@modrinth/ui";
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
useHead({
title: "Profile settings - Modrinth",
@@ -101,8 +104,6 @@ definePageMeta({
middleware: "auth",
});
const { formatMessage } = useVIntl();
const messages = defineMessages({
title: {
id: "settings.profile.profile-info",
@@ -222,7 +223,6 @@ async function saveChanges() {
saved.value = true;
} catch (err) {
addNotification({
group: "main",
title: "An error occurred",
text: err
? err.data

View File

@@ -57,12 +57,18 @@
</template>
<script setup>
import { XIcon } from "@modrinth/assets";
import { commonMessages, commonSettingsMessages, useRelativeTime } from "@modrinth/ui";
import {
commonMessages,
commonSettingsMessages,
injectNotificationManager,
useRelativeTime,
} from "@modrinth/ui";
definePageMeta({
middleware: "auth",
});
const { addNotification } = injectNotificationManager();
const { formatMessage } = useVIntl();
const formatRelativeTime = useRelativeTime();
@@ -102,7 +108,6 @@ useHead({
title: () => `${formatMessage(commonSettingsMessages.sessions)} - Modrinth`,
});
const data = useNuxtApp();
const { data: sessions, refresh } = await useAsyncData("session/list", () =>
useBaseFetch("session/list"),
);
@@ -116,8 +121,7 @@ async function revokeSession(id) {
});
await refresh();
} catch (err) {
data.$notify({
group: "main",
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: "error",

View File

@@ -120,7 +120,6 @@ export default defineNuxtPlugin((nuxtApp) => {
return 0;
});
});
nuxtApp.provide("notify", (notif) => addNotification(notif));
});
export const formatNumber = (number, abbreviate = true) => {
const x = +number;

View File

@@ -0,0 +1,51 @@
import { useState } from "#app";
import {
type NotificationPanelLocation,
type WebNotification,
AbstractWebNotificationManager,
} from "@modrinth/ui";
export class FrontendNotificationManager extends AbstractWebNotificationManager {
private readonly state: Ref<WebNotification[]>;
private readonly locationState: Ref<NotificationPanelLocation>;
public constructor() {
super();
this.state = useState<WebNotification[]>("notifications", () => []);
this.locationState = useState<NotificationPanelLocation>(
"notifications.location",
() => "right",
);
}
public getNotificationLocation(): NotificationPanelLocation {
return this.locationState.value;
}
public setNotificationLocation(location: NotificationPanelLocation): void {
this.locationState.value = location;
}
public getNotifications(): WebNotification[] {
return this.state.value;
}
protected addNotificationToStorage(notification: WebNotification): void {
this.state.value.push(notification);
}
protected removeNotificationFromStorage(id: string | number): void {
const index = this.state.value.findIndex((n) => n.id === id);
if (index > -1) {
this.state.value.splice(index, 1);
}
}
protected removeNotificationFromStorageByIndex(index: number): void {
this.state.value.splice(index, 1);
}
protected clearAllNotificationsFromStorage(): void {
this.state.value.splice(0);
}
}

View 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");