You've already forked AstralRinth
forked from didirus/AstralRinth
Merge commit '175b90be5a42e5bfd3289ffdfbf7b201404f82a8' into beta
This commit is contained in:
@@ -1,29 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { ButtonStyled } from "@modrinth/ui";
|
||||
import { MailIcon, CheckIcon } from "@modrinth/assets";
|
||||
import { ref, watchEffect } from "vue";
|
||||
import { ref } from "vue";
|
||||
import { useBaseFetch } from "~/composables/fetch.js";
|
||||
|
||||
const auth = await useAuth();
|
||||
const showSubscriptionConfirmation = ref(false);
|
||||
const subscribed = ref(false);
|
||||
|
||||
async function checkSubscribed() {
|
||||
if (auth.value?.user) {
|
||||
try {
|
||||
const { data } = await useBaseFetch("auth/email/subscribe", {
|
||||
method: "GET",
|
||||
});
|
||||
subscribed.value = data?.subscribed || false;
|
||||
} catch {
|
||||
subscribed.value = false;
|
||||
const showSubscribeButton = useAsyncData(
|
||||
async () => {
|
||||
if (auth.value?.user) {
|
||||
try {
|
||||
const { subscribed } = await useBaseFetch("auth/email/subscribe", {
|
||||
method: "GET",
|
||||
});
|
||||
return !subscribed;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
checkSubscribed();
|
||||
});
|
||||
},
|
||||
{ watch: [auth], server: false },
|
||||
);
|
||||
|
||||
async function subscribe() {
|
||||
try {
|
||||
@@ -35,14 +34,19 @@ async function subscribe() {
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
showSubscriptionConfirmation.value = false;
|
||||
subscribed.value = true;
|
||||
showSubscribeButton.status.value = "success";
|
||||
showSubscribeButton.data.value = false;
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ButtonStyled v-if="auth?.user && !subscribed" color="brand" type="outlined">
|
||||
<ButtonStyled
|
||||
v-if="showSubscribeButton.status.value === 'success' && showSubscribeButton.data.value"
|
||||
color="brand"
|
||||
type="outlined"
|
||||
>
|
||||
<button v-tooltip="`Subscribe to the Modrinth newsletter`" @click="subscribe">
|
||||
<template v-if="!showSubscriptionConfirmation"> <MailIcon /> Subscribe </template>
|
||||
<template v-else> <CheckIcon /> Subscribed! </template>
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
<template>
|
||||
<template v-if="moderation">
|
||||
<Chips v-model="reasonFilter" :items="reasons" />
|
||||
<p v-if="reports.length === MAX_REPORTS" class="text-red">
|
||||
There are at least {{ MAX_REPORTS }} open reports. This page is at its max reports and will
|
||||
not show any more recent ones.
|
||||
</p>
|
||||
<p v-else-if="reasonFilter === 'All'">There are {{ filteredReports.length }} open reports.</p>
|
||||
<p v-else>
|
||||
There are {{ filteredReports.length }}/{{ reports.length }} open '{{ reasonFilter }}' reports.
|
||||
</p>
|
||||
</template>
|
||||
<ReportInfo
|
||||
v-for="report in reports.filter(
|
||||
(x) =>
|
||||
(moderation || x.reporterUser.id === auth.user.id) &&
|
||||
(viewMode === 'open' ? x.open : !x.open),
|
||||
)"
|
||||
v-for="report in filteredReports"
|
||||
:key="report.id"
|
||||
:report="report"
|
||||
:thread="report.thread"
|
||||
:show-message="false"
|
||||
:moderation="moderation"
|
||||
raised
|
||||
:auth="auth"
|
||||
@@ -16,11 +24,12 @@
|
||||
<p v-if="reports.length === 0">You don't have any active reports.</p>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Chips } from "@modrinth/ui";
|
||||
import ReportInfo from "~/components/ui/report/ReportInfo.vue";
|
||||
import { addReportMessage } from "~/helpers/threads.js";
|
||||
import { asEncodedJsonArray, fetchSegmented } from "~/utils/fetch-helpers.ts";
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
moderation: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -32,9 +41,14 @@ defineProps({
|
||||
});
|
||||
|
||||
const viewMode = ref("open");
|
||||
const reasonFilter = ref("All");
|
||||
const reports = ref([]);
|
||||
|
||||
let { data: rawReports } = await useAsyncData("report", () => useBaseFetch("report?count=1000"));
|
||||
const MAX_REPORTS = 1500;
|
||||
|
||||
let { data: rawReports } = await useAsyncData("report", () =>
|
||||
useBaseFetch(`report?count=${MAX_REPORTS}`),
|
||||
);
|
||||
|
||||
rawReports = rawReports.value.map((report) => {
|
||||
report.item_id = report.item_id.replace(/"/g, "");
|
||||
@@ -51,6 +65,7 @@ const userIds = [...new Set(reporterUsers.concat(reportedUsers))];
|
||||
const threadIds = [
|
||||
...new Set(rawReports.filter((report) => report.thread_id).map((report) => report.thread_id)),
|
||||
];
|
||||
const reasons = ["All", ...new Set(rawReports.map((report) => report.report_type))];
|
||||
|
||||
const [{ data: users }, { data: versions }, { data: threads }] = await Promise.all([
|
||||
await useAsyncData(`users?ids=${JSON.stringify(userIds)}`, () =>
|
||||
@@ -93,4 +108,13 @@ reports.value = rawReports.map((report) => {
|
||||
report.open = true;
|
||||
return report;
|
||||
});
|
||||
|
||||
const filteredReports = computed(() =>
|
||||
reports.value?.filter(
|
||||
(x) =>
|
||||
(props.moderation || x.reporterUser.id === props.auth.user.id) &&
|
||||
(viewMode.value === "open" ? x.open : !x.open) &&
|
||||
(reasonFilter.value === "All" || reasonFilter.value === x.report_type),
|
||||
),
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -66,6 +66,27 @@
|
||||
<UiServersPanelSpinner />
|
||||
Your server's hardware is currently being upgraded and will be back online shortly.
|
||||
</div>
|
||||
<div
|
||||
v-else-if="status === 'suspended' && suspension_reason === 'cancelled'"
|
||||
class="relative -mt-4 flex w-full flex-col gap-2 rounded-b-3xl bg-bg-red p-4 text-sm font-bold text-contrast"
|
||||
>
|
||||
<div class="flex flex-row gap-2">
|
||||
<UiServersIconsPanelErrorIcon class="!size-5" /> Your server has been cancelled. Please
|
||||
update your billing information or contact Modrinth Support for more information.
|
||||
</div>
|
||||
<CopyCode :text="`${props.server_id}`" class="ml-auto" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="status === 'suspended' && suspension_reason"
|
||||
class="relative -mt-4 flex w-full flex-col gap-2 rounded-b-3xl bg-bg-red p-4 text-sm font-bold text-contrast"
|
||||
>
|
||||
<div class="flex flex-row gap-2">
|
||||
<UiServersIconsPanelErrorIcon class="!size-5" /> Your server has been suspended:
|
||||
{{ suspension_reason }}. Please update your billing information or contact Modrinth Support
|
||||
for more information.
|
||||
</div>
|
||||
<CopyCode :text="`${props.server_id}`" class="ml-auto" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="status === 'suspended'"
|
||||
class="relative -mt-4 flex w-full flex-col gap-2 rounded-b-3xl bg-bg-red p-4 text-sm font-bold text-contrast"
|
||||
@@ -87,7 +108,8 @@ import { Avatar, CopyCode } from "@modrinth/ui";
|
||||
|
||||
const props = defineProps<Partial<Server>>();
|
||||
|
||||
if (props.server_id) {
|
||||
if (props.server_id && props.status === "available") {
|
||||
// Necessary only to get server icon
|
||||
await useModrinthServers(props.server_id, ["general"]);
|
||||
}
|
||||
|
||||
@@ -109,11 +131,6 @@ if (props.upstream) {
|
||||
}
|
||||
|
||||
const image = useState<string | undefined>(`server-icon-${props.server_id}`, () => undefined);
|
||||
|
||||
if (import.meta.server && projectData.value?.icon_url) {
|
||||
await useModrinthServers(props.server_id!, ["general"]);
|
||||
}
|
||||
|
||||
const iconUrl = computed(() => projectData.value?.icon_url || undefined);
|
||||
const isConfiguring = computed(() => props.flows?.intro);
|
||||
</script>
|
||||
|
||||
@@ -34,6 +34,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal ref="modalReply" header="Reply to thread">
|
||||
<div class="modal-submit universal-body">
|
||||
<span>
|
||||
Your project is already approved. As such, the moderation team does not actively monitor
|
||||
this thread. However, they may still see your message if there is a problem with your
|
||||
project.
|
||||
</span>
|
||||
<span>
|
||||
If you need to get in contact with the moderation team, please use the
|
||||
<a class="text-link" href="https://support.modrinth.com" target="_blank">
|
||||
Modrinth Help Center
|
||||
</a>
|
||||
and click the green bubble to contact support.
|
||||
</span>
|
||||
<Checkbox
|
||||
v-model="replyConfirmation"
|
||||
description="Confirm moderators do not actively monitor this"
|
||||
>
|
||||
I acknowledge that the moderators do not actively monitor the thread.
|
||||
</Checkbox>
|
||||
<div class="input-group push-right">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="!replyConfirmation"
|
||||
@click="sendReplyFromModal()"
|
||||
>
|
||||
<ReplyIcon aria-hidden="true" />
|
||||
Reply to thread
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<div v-if="flags.developerMode" class="thread-id">
|
||||
Thread ID:
|
||||
<CopyCode :text="thread.id" />
|
||||
@@ -71,12 +103,17 @@
|
||||
v-if="sortedMessages.length > 0"
|
||||
class="btn btn-primary"
|
||||
:disabled="!replyBody"
|
||||
@click="sendReply()"
|
||||
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
|
||||
>
|
||||
<ReplyIcon aria-hidden="true" />
|
||||
Reply
|
||||
</button>
|
||||
<button v-else class="btn btn-primary" :disabled="!replyBody" @click="sendReply()">
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-primary"
|
||||
:disabled="!replyBody"
|
||||
@click="isApproved(project) && !isStaff(auth.user) ? openReplyModal() : sendReply()"
|
||||
>
|
||||
<SendIcon aria-hidden="true" />
|
||||
Send
|
||||
</button>
|
||||
@@ -289,6 +326,7 @@ const sortedMessages = computed(() => {
|
||||
});
|
||||
|
||||
const modalSubmit = ref(null);
|
||||
const modalReply = ref(null);
|
||||
|
||||
async function updateThreadLocal() {
|
||||
let threadId = null;
|
||||
@@ -316,6 +354,11 @@ async function onUploadImage(file) {
|
||||
return response.url;
|
||||
}
|
||||
|
||||
async function sendReplyFromModal(status = null, privateMessage = false) {
|
||||
modalReply.value.hide();
|
||||
await sendReply(status, privateMessage);
|
||||
}
|
||||
|
||||
async function sendReply(status = null, privateMessage = false) {
|
||||
try {
|
||||
const body = {
|
||||
@@ -398,6 +441,7 @@ async function reopenReport() {
|
||||
|
||||
const replyWithSubmission = ref(false);
|
||||
const submissionConfirmation = ref(false);
|
||||
const replyConfirmation = ref(false);
|
||||
|
||||
function openResubmitModal(reply) {
|
||||
submissionConfirmation.value = false;
|
||||
@@ -405,6 +449,11 @@ function openResubmitModal(reply) {
|
||||
modalSubmit.value.show();
|
||||
}
|
||||
|
||||
function openReplyModal(reply) {
|
||||
replyConfirmation.value = false;
|
||||
modalReply.value.show();
|
||||
}
|
||||
|
||||
async function resubmit() {
|
||||
if (replyWithSubmission.value) {
|
||||
await sendReply("processing");
|
||||
|
||||
@@ -182,9 +182,6 @@
|
||||
"collection.button.unfollow-project": {
|
||||
"message": "Unfollow project"
|
||||
},
|
||||
"collection.button.upload-icon": {
|
||||
"message": "Upload icon"
|
||||
},
|
||||
"collection.delete-modal.description": {
|
||||
"message": "This will remove this collection forever. This action cannot be undone."
|
||||
},
|
||||
|
||||
@@ -951,14 +951,7 @@ import {
|
||||
useRelativeTime,
|
||||
} from "@modrinth/ui";
|
||||
import VersionSummary from "@modrinth/ui/src/components/version/VersionSummary.vue";
|
||||
import {
|
||||
formatCategory,
|
||||
formatProjectType,
|
||||
isRejected,
|
||||
isStaff,
|
||||
isUnderReview,
|
||||
renderString,
|
||||
} from "@modrinth/utils";
|
||||
import { formatCategory, formatProjectType, renderString } from "@modrinth/utils";
|
||||
import dayjs from "dayjs";
|
||||
import { Tooltip } from "floating-vue";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
@@ -1646,9 +1639,7 @@ const navLinks = computed(() => {
|
||||
{
|
||||
label: formatMessage(messages.moderationTab),
|
||||
href: `${projectUrl}/moderation`,
|
||||
shown:
|
||||
!!currentMember.value &&
|
||||
(isRejected(project.value) || isUnderReview(project.value) || isStaff(auth.value.user)),
|
||||
shown: !!currentMember.value,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -76,8 +76,15 @@
|
||||
<p>
|
||||
This is a private conversation thread with the Modrinth moderators. They may message you
|
||||
with issues concerning this project. This thread is only checked when you submit your
|
||||
project for review. For additional inquiries, contact
|
||||
<a href="https://support.modrinth.com">Modrinth Support</a>.
|
||||
project for review. For additional inquiries, please go to the
|
||||
<a class="text-link" href="https://support.modrinth.com" target="_blank">
|
||||
Modrinth Help Center
|
||||
</a>
|
||||
and click the green bubble to contact support.
|
||||
</p>
|
||||
<p v-if="isApproved(project)" class="warning">
|
||||
<IssuesIcon /> The moderators do not actively monitor this chat. However, they may still see
|
||||
messages here if there is a problem with your project.
|
||||
</p>
|
||||
<ConversationThread
|
||||
v-if="thread"
|
||||
|
||||
@@ -58,6 +58,41 @@
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
<NewModal ref="modifyModal">
|
||||
<template #title>
|
||||
<span class="text-lg font-extrabold text-contrast">Modify charge</span>
|
||||
</template>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="cancel" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-contrast">
|
||||
Cancel server
|
||||
<span class="text-brand-red">*</span>
|
||||
</span>
|
||||
<span>
|
||||
Whether or not the subscription should be cancelled. Submitting this as "true" will
|
||||
cancel the subscription, while submitting it as "false" will force another charge
|
||||
attempt to be made.
|
||||
</span>
|
||||
</label>
|
||||
<Toggle id="cancel" v-model="cancel" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="modifying" @click="modifyCharge">
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Modify charge
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button @click="modifyModal.hide()">
|
||||
<XIcon aria-hidden="true" />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
<div class="page experimental-styles-within">
|
||||
<div
|
||||
class="mb-4 flex items-center justify-between border-0 border-b border-solid border-divider pb-4"
|
||||
@@ -201,6 +236,12 @@
|
||||
Refund options
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else-if="charge.status === 'failed'" color="red" color-fill="text">
|
||||
<button @click="showModifyModal(subscription)">
|
||||
<CurrencyIcon />
|
||||
Modify charge
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -234,7 +275,6 @@ import { products } from "~/generated/state.json";
|
||||
import ModrinthServersIcon from "~/components/ui/servers/ModrinthServersIcon.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const data = useNuxtApp();
|
||||
const vintl = useVIntl();
|
||||
|
||||
const { formatMessage } = vintl;
|
||||
@@ -304,6 +344,10 @@ const refundTypes = ref(["full", "partial", "none"]);
|
||||
const refundAmount = ref(0);
|
||||
const unprovision = ref(true);
|
||||
|
||||
const modifying = ref(false);
|
||||
const modifyModal = ref();
|
||||
const cancel = ref(false);
|
||||
|
||||
function showRefundModal(charge) {
|
||||
selectedCharge.value = charge;
|
||||
refundType.value = "full";
|
||||
@@ -312,6 +356,12 @@ function showRefundModal(charge) {
|
||||
refundModal.value.show();
|
||||
}
|
||||
|
||||
function showModifyModal(charge) {
|
||||
selectedCharge.value = charge;
|
||||
cancel.value = false;
|
||||
modifyModal.value.show();
|
||||
}
|
||||
|
||||
async function refundCharge() {
|
||||
refunding.value = true;
|
||||
try {
|
||||
@@ -327,8 +377,7 @@ async function refundCharge() {
|
||||
await refreshCharges();
|
||||
refundModal.value.hide();
|
||||
} catch (err) {
|
||||
data.$notify({
|
||||
group: "main",
|
||||
addNotification({
|
||||
title: "Error refunding",
|
||||
text: err.data?.description ?? err,
|
||||
type: "error",
|
||||
@@ -337,6 +386,32 @@ async function refundCharge() {
|
||||
refunding.value = false;
|
||||
}
|
||||
|
||||
async function modifyCharge() {
|
||||
modifying.value = true;
|
||||
try {
|
||||
await useBaseFetch(`billing/subscription/${selectedCharge.value.id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({
|
||||
cancelled: cancel.value,
|
||||
}),
|
||||
internal: true,
|
||||
});
|
||||
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",
|
||||
});
|
||||
await refreshCharges();
|
||||
} catch (err) {
|
||||
addNotification({
|
||||
title: "Error reattempting charge",
|
||||
text: err.data?.description ?? err,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
modifying.value = false;
|
||||
}
|
||||
|
||||
const chargeStatuses = {
|
||||
open: {
|
||||
color: "bg-blue",
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
@change="showPreviewImage"
|
||||
>
|
||||
<UploadIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.uploadIconButton) }}
|
||||
</FileInput>
|
||||
<Button
|
||||
v-if="!deletedIcon && (previewImage || collection.icon_url)"
|
||||
@@ -479,10 +478,6 @@ const messages = defineMessages({
|
||||
id: "collection.label.updated-at",
|
||||
defaultMessage: "Updated {ago}",
|
||||
},
|
||||
uploadIconButton: {
|
||||
id: "collection.button.upload-icon",
|
||||
defaultMessage: "Upload icon",
|
||||
},
|
||||
});
|
||||
|
||||
const data = useNuxtApp();
|
||||
|
||||
@@ -96,16 +96,7 @@
|
||||
<UiServersServerListing
|
||||
v-for="server in filteredData"
|
||||
:key="server.server_id"
|
||||
:server_id="server.server_id"
|
||||
:name="server.name"
|
||||
:status="server.status"
|
||||
:game="server.game"
|
||||
:loader="server.loader"
|
||||
:loader_version="server.loader_version"
|
||||
:mc_version="server.mc_version"
|
||||
:upstream="server.upstream"
|
||||
:net="server.net"
|
||||
:flows="server.flows"
|
||||
v-bind="server"
|
||||
/>
|
||||
<LazyUiServersServerListingSkeleton v-if="isPollingForNewServers" />
|
||||
</ul>
|
||||
|
||||
@@ -208,15 +208,7 @@
|
||||
<div class="flex flex-col gap-2">
|
||||
<UiServersServerListing
|
||||
v-if="subscription.serverInfo"
|
||||
:server_id="subscription.serverInfo.server_id"
|
||||
:name="subscription.serverInfo.name"
|
||||
:status="subscription.serverInfo.status"
|
||||
:game="subscription.serverInfo.game"
|
||||
:loader="subscription.serverInfo.loader"
|
||||
:loader_version="subscription.serverInfo.loader_version"
|
||||
:mc_version="subscription.serverInfo.mc_version"
|
||||
:upstream="subscription.serverInfo.upstream"
|
||||
:net="subscription.serverInfo.net"
|
||||
v-bind="subscription.serverInfo"
|
||||
/>
|
||||
<div v-else class="w-fit">
|
||||
<p>
|
||||
|
||||
Reference in New Issue
Block a user