You've already forked AstralRinth
forked from didirus/AstralRinth
feat: Moderation Dashboard Overhaul (#4059)
* feat: Moderation Dashboard Overhaul * fix: lint issues * fix: issues * fix: report layout * fix: lint * fix: impl quick replies * fix: remove test qr * feat: individual report page + use new backend * feat: memoize filtering * feat: apply optimizations to moderation queue * fix: lint issues * feat: impl quick reply functionality * fix: top level await * fix: dep issue * fix: dep issue x2 * fix: dep issue * feat: intl extract * fix: dev-187 * fix: dev-186 & review project btn * fix: dev-176 * remove redundant moderation button from user dropdown * correct a msg and add admin to read filter --------- Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div
|
||||
class="universal-card flex min-h-[6rem] flex-col justify-between gap-3 rounded-lg p-4 sm:h-24 sm:flex-row sm:items-center sm:gap-0"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div class="flex-shrink-0 rounded-lg">
|
||||
<Avatar size="48px" :src="queueEntry.project.icon_url" />
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-1 flex-col">
|
||||
<h3 class="truncate text-lg font-semibold">
|
||||
{{ queueEntry.project.name }}
|
||||
</h3>
|
||||
<nuxt-link
|
||||
v-if="queueEntry.owner"
|
||||
target="_blank"
|
||||
class="flex items-center gap-1 truncate align-middle text-sm hover:text-brand"
|
||||
:to="`/user/${queueEntry.owner.user.username}`"
|
||||
>
|
||||
<Avatar
|
||||
:src="queueEntry.owner.user.avatar_url"
|
||||
circle
|
||||
size="16px"
|
||||
class="inline-block flex-shrink-0"
|
||||
/>
|
||||
<span class="truncate">{{ queueEntry.owner.user.username }}</span>
|
||||
</nuxt-link>
|
||||
<nuxt-link
|
||||
v-else-if="queueEntry.org"
|
||||
target="_blank"
|
||||
class="flex items-center gap-1 truncate align-middle text-sm hover:text-brand"
|
||||
:to="`/organization/${queueEntry.org.slug}`"
|
||||
>
|
||||
<Avatar
|
||||
:src="queueEntry.org.icon_url"
|
||||
circle
|
||||
size="16px"
|
||||
class="inline-block flex-shrink-0"
|
||||
/>
|
||||
<span class="truncate">{{ queueEntry.org.name }}</span>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-1">
|
||||
<span class="flex items-center gap-1 whitespace-nowrap text-sm">
|
||||
<BoxIcon
|
||||
v-if="queueEntry.project.project_type === 'mod'"
|
||||
class="size-4 flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<PaintbrushIcon
|
||||
v-else-if="queueEntry.project.project_type === 'resourcepack'"
|
||||
class="size-4 flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<BracesIcon
|
||||
v-else-if="queueEntry.project.project_type === 'datapack'"
|
||||
class="size-4 flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<PackageOpenIcon
|
||||
v-else-if="queueEntry.project.project_type === 'modpack'"
|
||||
class="size-4 flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<GlassesIcon
|
||||
v-else-if="queueEntry.project.project_type === 'shader'"
|
||||
class="size-4 flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<PlugIcon
|
||||
v-else-if="queueEntry.project.project_type === 'plugin'"
|
||||
class="size-4 flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="hidden sm:inline">{{
|
||||
props.queueEntry.project.project_types.map(formatProjectType).join(", ")
|
||||
}}</span>
|
||||
<span class="sm:hidden">{{
|
||||
formatProjectType(props.queueEntry.project.project_type ?? "project").substring(0, 3)
|
||||
}}</span>
|
||||
</span>
|
||||
|
||||
<span class="hidden text-sm sm:inline">•</span>
|
||||
|
||||
<div class="flex flex-row gap-2 text-sm">
|
||||
Requesting
|
||||
<Badge
|
||||
v-if="props.queueEntry.project.requested_status"
|
||||
:type="props.queueEntry.project.requested_status"
|
||||
class="status"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span class="hidden text-sm sm:inline">•</span>
|
||||
|
||||
<span
|
||||
v-tooltip="`Since ${queuedDate.toLocaleString()}`"
|
||||
class="truncate text-sm"
|
||||
:class="{
|
||||
'text-red': daysInQueue > 4,
|
||||
'text-orange': daysInQueue > 2,
|
||||
}"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ getSubmittedTime(queueEntry) }}</span>
|
||||
<span class="sm:hidden">{{
|
||||
getSubmittedTime(queueEntry).replace("Submitted ", "")
|
||||
}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-2 sm:justify-start">
|
||||
<ButtonStyled circular>
|
||||
<NuxtLink target="_blank" :to="`/project/${queueEntry.project.slug}`">
|
||||
<EyeIcon class="size-4" />
|
||||
</NuxtLink>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular color="orange" @click="openProjectForReview">
|
||||
<button>
|
||||
<ScaleIcon class="size-4" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
EyeIcon,
|
||||
PaintbrushIcon,
|
||||
ScaleIcon,
|
||||
BoxIcon,
|
||||
GlassesIcon,
|
||||
PlugIcon,
|
||||
PackageOpenIcon,
|
||||
BracesIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { useRelativeTime, Avatar, ButtonStyled, Badge } from "@modrinth/ui";
|
||||
import {
|
||||
formatProjectType,
|
||||
type Organization,
|
||||
type Project,
|
||||
type TeamMember,
|
||||
} from "@modrinth/utils";
|
||||
import { computed } from "vue";
|
||||
import { useModerationStore } from "~/store/moderation.ts";
|
||||
import type { ModerationProject } from "~/helpers/moderation";
|
||||
|
||||
const formatRelativeTime = useRelativeTime();
|
||||
const moderationStore = useModerationStore();
|
||||
|
||||
const props = defineProps<{
|
||||
queueEntry: ModerationProject;
|
||||
}>();
|
||||
|
||||
function getDaysQueued(date: Date): number {
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
const queuedDate = computed(() => {
|
||||
return dayjs(
|
||||
props.queueEntry.project.queued ||
|
||||
props.queueEntry.project.created ||
|
||||
props.queueEntry.project.updated,
|
||||
);
|
||||
});
|
||||
|
||||
const daysInQueue = computed(() => {
|
||||
return getDaysQueued(queuedDate.value.toDate());
|
||||
});
|
||||
|
||||
function openProjectForReview() {
|
||||
moderationStore.setSingleProject(props.queueEntry.project.id);
|
||||
navigateTo({
|
||||
name: "type-id",
|
||||
params: {
|
||||
type: "project",
|
||||
id: props.queueEntry.project.id,
|
||||
},
|
||||
state: {
|
||||
showChecklist: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getSubmittedTime(project: any): string {
|
||||
const date =
|
||||
props.queueEntry.project.queued ||
|
||||
props.queueEntry.project.created ||
|
||||
props.queueEntry.project.updated;
|
||||
if (!date) return "Unknown";
|
||||
|
||||
try {
|
||||
return `Submitted ${formatRelativeTime(dayjs(date).toISOString())}`;
|
||||
} catch {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user