mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-01-12 14:06:14 +08:00
implement admin flags page
This commit is contained in:
parent
1d9492511f
commit
908c3e6944
@ -15,6 +15,7 @@ Stuff that needs to be done before I consider this a successful POC
|
||||
- [ ] maybe deployment alongside the existing frontend? (server is working now)
|
||||
- [ ] figure out why vite isn't serving the manifest
|
||||
- [ ] cors?
|
||||
- [ ] i18n + ssr, server needs local for both logged in and anon user, date formatting needs to go thru i18n
|
||||
- [x] investigate why eslint/prettier don't auto fix
|
||||
|
||||
## Big list of pages!
|
||||
@ -211,9 +212,9 @@ once QA has passed, the checkboxes can be removed and the page can be ~~striked
|
||||
- [ ] qa
|
||||
- admin (empty)
|
||||
- flags
|
||||
- [ ] fetch
|
||||
- [ ] layout
|
||||
- [ ] functionality
|
||||
- [x] fetch
|
||||
- [x] layout
|
||||
- [x] functionality (cors error)
|
||||
- [ ] design
|
||||
- [ ] qa
|
||||
- health
|
||||
|
9
frontend-new/src/components/UserAvatar.vue
Normal file
9
frontend-new/src/components/UserAvatar.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
username: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{ username }}'s avatar</span>
|
||||
</template>
|
@ -1,7 +1,7 @@
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { PaginatedResult, Project, User } from "hangar-api";
|
||||
import { useInitialState } from "~/composables/useInitialState";
|
||||
import { HangarNotification, Invites } from "hangar-internal";
|
||||
import { Flag, HangarNotification, Invites } from "hangar-internal";
|
||||
|
||||
export async function useProjects(pagination = { limit: 25, offset: 0 }, blocking = true) {
|
||||
return await useInitialState("useProjects", () => useApi<PaginatedResult<Project>>("projects", false, "get", pagination), blocking);
|
||||
@ -30,3 +30,7 @@ export async function useInvites(blocking = true) {
|
||||
export async function useNotifications(blocking = true) {
|
||||
return await useInitialState("useNotifications", () => useInternalApi<HangarNotification[]>("notifications", false), blocking);
|
||||
}
|
||||
|
||||
export async function useFlags(blocking = true) {
|
||||
return await useInitialState("useFlags", () => useInternalApi<Flag[]>("flags/", false), blocking);
|
||||
}
|
||||
|
@ -1,5 +1,68 @@
|
||||
<script lang="ts" setup>
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useFlags } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { ref } from "vue";
|
||||
import { Flag } from "hangar-internal";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
import { prettyDateTime } from "~/composables/useDate";
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const { params } = useRoute();
|
||||
const flags = await useFlags().catch((e) => handleRequestError(e, ctx, i18n));
|
||||
const loading = ref<{ [key: number]: boolean }>({});
|
||||
if (flags && flags.value) {
|
||||
for (const flag of flags.value) {
|
||||
loading.value[flag.id] = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolve(flag: Flag) {
|
||||
loading.value[flag.id] = true;
|
||||
useInternalApi<Flag[]>(`flags/${flag.id}/resolve/true`, false, "POST")
|
||||
.catch<any>((e) => handleRequestError(e, ctx, i18n))
|
||||
.then(async () => {
|
||||
if (flags && flags.value) {
|
||||
const newFlags = await useInternalApi<Flag[]>("flags/", false).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
if (newFlags) {
|
||||
flags.value = newFlags;
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value[flag.id] = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>flags</h1>
|
||||
<h1>{{ i18n.t("flagReview.title") }}</h1>
|
||||
<template v-if="flags.length > 0">
|
||||
<div v-for="flag in flags" :key="flag.id">
|
||||
<UserAvatar :username="flag.reportedByName"></UserAvatar>
|
||||
<h2>
|
||||
<!-- this is client only, as the date format causes hydration mismatches... -->
|
||||
<!-- I think the proper fix is using vue-i18n for date format -->
|
||||
<ClientOnly>{{
|
||||
i18n.t("flagReview.line1", [flag.reportedByName, `${flag.projectNamespace.owner}/${flag.projectNamespace.slug}`, prettyDateTime(flag.createdAt)])
|
||||
}}</ClientOnly>
|
||||
<router-link :to="`/${flag.projectNamespace.owner}/${flag.projectNamespace.slug}`" target="_blank">open in new </router-link>
|
||||
</h2>
|
||||
<h3>{{ i18n.t("flagReview.line2", [i18n.t(flag.reason)]) }}</h3>
|
||||
<h3>{{ i18n.t("flagReview.line3", [flag.comment]) }}</h3>
|
||||
<a fixHref="$util.forumUrl(flag.reportedByName)">{{ i18n.t("flagReview.msgUser") }}</a>
|
||||
<a fixHref="$util.forumUrl(flag.projectNamespace.owner)">{{ i18n.t("flagReview.msgProjectOwner") }}</a>
|
||||
<!-- todo modal for visibility change -->
|
||||
<button :disabled="loading[flag.id]" @click="resolve(flag)">{{ i18n.t("flagReview.markResolved") }}</button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>
|
||||
{{ i18n.t("flagReview.noFlags") }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
|
Loading…
Reference in New Issue
Block a user