mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-11-27 06:01:08 +08:00
fix(frontend): cleanup error handling even more
This commit is contained in:
parent
077df236fd
commit
88e0a41469
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/backend/src/main/java" charset="UTF-8" />
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
@ -20,7 +20,7 @@ const props = defineProps<{
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const flags = await (props.resolved ? useResolvedFlags() : useUnresolvedFlags()).catch((e) => handleRequestError(e));
|
||||
const flags = await (props.resolved ? useResolvedFlags() : useUnresolvedFlags());
|
||||
const loading = ref<{ [key: number]: boolean }>({});
|
||||
|
||||
function resolve(flag: Flag) {
|
||||
@ -45,6 +45,7 @@ function resolve(flag: Flag) {
|
||||
// TODO: bake into hangarflag?
|
||||
const notifications = ref<HangarFlagNotification[]>([]);
|
||||
const currentId = ref(-1);
|
||||
|
||||
async function getNotifications(flag: Flag) {
|
||||
if (currentId.value === flag.id) {
|
||||
return;
|
||||
@ -83,7 +84,10 @@ async function getNotifications(flag: Flag) {
|
||||
|
||||
<template v-if="resolved">
|
||||
<Button v-if="currentId !== item.id" @click="getNotifications(item)">Load notifications</Button>
|
||||
<Button :disabled="loading[item.id]" @click="resolve(item)"><IconMdiCheck class="mr-1" /> {{ i18n.t("flagReview.markUnresolved") }}</Button>
|
||||
<Button :disabled="loading[item.id]" @click="resolve(item)">
|
||||
<IconMdiCheck class="mr-1" />
|
||||
{{ i18n.t("flagReview.markUnresolved") }}
|
||||
</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex flex-col space-y-1">
|
||||
@ -91,7 +95,10 @@ async function getNotifications(flag: Flag) {
|
||||
<ReportNotificationModal :flag="item" :send-to-reporter="true" />
|
||||
</div>
|
||||
<VisibilityChangerModal :prop-visibility="item.projectVisibility" type="project" :post-url="`projects/visibility/${item.projectId}`" />
|
||||
<Button :disabled="loading[item.id]" @click="resolve(item)"><IconMdiCheck class="mr-1" /> {{ i18n.t("flagReview.markResolved") }}</Button>
|
||||
<Button :disabled="loading[item.id]" @click="resolve(item)">
|
||||
<IconMdiCheck class="mr-1" />
|
||||
{{ i18n.t("flagReview.markResolved") }}
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
@ -122,25 +122,23 @@ function updateNotifications() {
|
||||
unreadNotifications.value = v.value;
|
||||
}
|
||||
});
|
||||
useRecentNotifications(30)
|
||||
.then((v) => {
|
||||
if (v && v.value) {
|
||||
// Only show notifications that are recent or unread (from the last 30 notifications)
|
||||
let filteredAmount = 0;
|
||||
notifications.value = v.value.filter((notification: HangarNotification) => {
|
||||
if (filteredAmount < 8 && (!notification.read || isRecent(notification.createdAt))) {
|
||||
if (!notification.read) {
|
||||
loadedUnreadNotifications.value++;
|
||||
}
|
||||
|
||||
filteredAmount++;
|
||||
return true;
|
||||
useRecentNotifications(30).then((v) => {
|
||||
if (v && v.value) {
|
||||
// Only show notifications that are recent or unread (from the last 30 notifications)
|
||||
let filteredAmount = 0;
|
||||
notifications.value = v.value.filter((notification: HangarNotification) => {
|
||||
if (filteredAmount < 8 && (!notification.read || isRecent(notification.createdAt))) {
|
||||
if (!notification.read) {
|
||||
loadedUnreadNotifications.value++;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => handleRequestError(e));
|
||||
|
||||
filteredAmount++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isRecent(date: string): boolean {
|
||||
|
@ -16,160 +16,179 @@ import {
|
||||
RoleTable,
|
||||
} from "hangar-internal";
|
||||
import { AsyncData } from "nuxt/app";
|
||||
import { ref, Ref } from "vue";
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { useAsyncData } from "#imports";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
|
||||
export async function useProjects(params: Record<string, any> = { limit: 25, offset: 0 }) {
|
||||
export async function useProjects(params: Record<string, any> = { limit: 25, offset: 0 }): Promise<Ref<PaginatedResult<Project> | null>> {
|
||||
return extract(await useAsyncData("useProjects", () => useApi<PaginatedResult<Project>>("projects", "get", params)));
|
||||
}
|
||||
|
||||
export async function useUser(user: string) {
|
||||
export async function useUser(user: string): Promise<Ref<User | null>> {
|
||||
return extract(await useAsyncData("useUser:" + user, () => useApi<User>("users/" + user)));
|
||||
}
|
||||
|
||||
export async function useOrganization(user: string) {
|
||||
export async function useOrganization(user: string): Promise<Ref<Organization | null>> {
|
||||
return extract(await useAsyncData("useOrganization:" + user, () => useInternalApi<Organization>(`organizations/org/${user}`)));
|
||||
}
|
||||
|
||||
export async function useProject(user: string, project: string) {
|
||||
export async function useProject(user: string, project: string): Promise<Ref<HangarProject | null>> {
|
||||
return extract(await useAsyncData("useProject:" + user + ":" + project, () => useInternalApi<HangarProject>("projects/project/" + user + "/" + project)));
|
||||
}
|
||||
|
||||
export async function useStargazers(user: string, project: string) {
|
||||
export async function useStargazers(user: string, project: string): Promise<Ref<PaginatedResult<User> | null>> {
|
||||
return extract(await useAsyncData("useStargazers:" + user + ":" + project, () => useApi<PaginatedResult<User>>(`projects/${user}/${project}/stargazers`)));
|
||||
}
|
||||
|
||||
export async function useWatchers(user: string, project: string) {
|
||||
export async function useWatchers(user: string, project: string): Promise<Ref<PaginatedResult<User> | null>> {
|
||||
return extract(await useAsyncData("useWatchers:" + user + ":" + project, () => useApi<PaginatedResult<User>>(`projects/${user}/${project}/watchers`)));
|
||||
}
|
||||
|
||||
export async function useStaff(params?: { offset?: number; limit?: number; sort?: string[] }) {
|
||||
export async function useStaff(params?: { offset?: number; limit?: number; sort?: string[] }): Promise<Ref<PaginatedResult<User> | null>> {
|
||||
return extract(await useAsyncData("useStaff", () => useApi<PaginatedResult<User>>("staff", "GET", params)));
|
||||
}
|
||||
|
||||
export async function useAuthors(params?: { offset?: number; limit?: number; sort?: string[] }) {
|
||||
export async function useAuthors(params?: { offset?: number; limit?: number; sort?: string[] }): Promise<Ref<PaginatedResult<User> | null>> {
|
||||
return extract(await useAsyncData("useAuthors", () => useApi<PaginatedResult<User>>("authors", "GET", params)));
|
||||
}
|
||||
|
||||
export async function useUsers() {
|
||||
export async function useUsers(): Promise<Ref<PaginatedResult<User> | null>> {
|
||||
return extract(await useAsyncData("useUsers", () => useApi<PaginatedResult<User>>("users")));
|
||||
}
|
||||
|
||||
export async function useInvites() {
|
||||
export async function useInvites(): Promise<Ref<Invites | null>> {
|
||||
return extract(await useAsyncData("useInvites", () => useInternalApi<Invites>("invites")));
|
||||
}
|
||||
|
||||
export async function useNotifications() {
|
||||
export async function useNotifications(): Promise<Ref<PaginatedResult<HangarNotification> | null>> {
|
||||
return extract(await useAsyncData("useNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("notifications")));
|
||||
}
|
||||
|
||||
export async function useUnreadNotifications() {
|
||||
export async function useUnreadNotifications(): Promise<Ref<PaginatedResult<HangarNotification> | null>> {
|
||||
return extract(await useAsyncData("useUnreadNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("unreadnotifications")));
|
||||
}
|
||||
|
||||
export async function useReadNotifications() {
|
||||
export async function useReadNotifications(): Promise<Ref<PaginatedResult<HangarNotification> | null>> {
|
||||
return extract(await useAsyncData("useReadNotifications", () => useInternalApi<PaginatedResult<HangarNotification>>("readnotifications")));
|
||||
}
|
||||
|
||||
export async function useRecentNotifications(amount: number) {
|
||||
export async function useRecentNotifications(amount: number): Promise<Ref<HangarNotification[] | null>> {
|
||||
return extract(await useAsyncData("useRecentNotifications:" + amount, () => useInternalApi<HangarNotification[]>("recentnotifications?amount=" + amount)));
|
||||
}
|
||||
|
||||
export async function useUnreadNotificationsCount() {
|
||||
export async function useUnreadNotificationsCount(): Promise<Ref<number | null>> {
|
||||
return extract(await useAsyncData("useUnreadNotificationsCount", () => useInternalApi<number>("unreadcount")));
|
||||
}
|
||||
|
||||
export async function useResolvedFlags() {
|
||||
export async function useResolvedFlags(): Promise<Ref<PaginatedResult<Flag> | null>> {
|
||||
return extract(await useAsyncData("useResolvedFlags", () => useInternalApi<PaginatedResult<Flag>>("flags/resolved")));
|
||||
}
|
||||
|
||||
export async function useUnresolvedFlags() {
|
||||
export async function useUnresolvedFlags(): Promise<Ref<PaginatedResult<Flag> | null>> {
|
||||
return extract(await useAsyncData("useUnresolvedFlags", () => useInternalApi<PaginatedResult<Flag>>("flags/unresolved")));
|
||||
}
|
||||
|
||||
export async function useProjectFlags(projectId: number) {
|
||||
export async function useProjectFlags(projectId: number): Promise<Ref<Flag[] | null>> {
|
||||
return extract(await useAsyncData("useProjectFlags:" + projectId, () => useInternalApi<Flag[]>("flags/" + projectId)));
|
||||
}
|
||||
|
||||
export async function useProjectNotes(projectId: number) {
|
||||
export async function useProjectNotes(projectId: number): Promise<Ref<Note[] | null>> {
|
||||
return extract(await useAsyncData("useProjectNotes:" + projectId, () => useInternalApi<Note[]>("projects/notes/" + projectId)));
|
||||
}
|
||||
|
||||
export async function useProjectChannels(user: string, project: string) {
|
||||
export async function useProjectChannels(user: string, project: string): Promise<Ref<ProjectChannel[] | null>> {
|
||||
return extract(await useAsyncData("useProjectChannels:" + user + ":" + project, () => useInternalApi<ProjectChannel[]>(`channels/${user}/${project}`)));
|
||||
}
|
||||
|
||||
export async function useProjectVersions(user: string, project: string) {
|
||||
export async function useProjectVersions(user: string, project: string): Promise<Ref<PaginatedResult<Version> | null>> {
|
||||
return extract(
|
||||
await useAsyncData("useProjectVersions:" + user + ":" + project, () => useApi<PaginatedResult<Version>>(`projects/${user}/${project}/versions`))
|
||||
);
|
||||
}
|
||||
|
||||
export async function useProjectVersionsInternal(user: string, project: string, version: string) {
|
||||
return await useAsyncData("useProjectVersionsInternal:" + user + ":" + project + ":" + version, () =>
|
||||
useInternalApi<HangarVersion>(`versions/version/${user}/${project}/versions/${version}`)
|
||||
export async function useProjectVersionsInternal(user: string, project: string, version: string): Promise<Ref<HangarVersion | null>> {
|
||||
return extract(
|
||||
await useAsyncData("useProjectVersionsInternal:" + user + ":" + project + ":" + version, () =>
|
||||
useInternalApi<HangarVersion>(`versions/version/${user}/${project}/versions/${version}`)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function usePage(user: string, project: string, path?: string) {
|
||||
return await useAsyncData("usePage:" + user + ":" + project + ":" + path, () =>
|
||||
useInternalApi<ProjectPage>(`pages/page/${user}/${project}` + (path ? "/" + path : ""))
|
||||
export async function usePage(user: string, project: string, path?: string): Promise<Ref<ProjectPage | null>> {
|
||||
return extract(
|
||||
await useAsyncData("usePage:" + user + ":" + project + ":" + path, () =>
|
||||
useInternalApi<ProjectPage>(`pages/page/${user}/${project}` + (path ? "/" + path : ""))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function useHealthReport() {
|
||||
export async function useHealthReport(): Promise<Ref<HealthReport | null>> {
|
||||
return extract(await useAsyncData("useHealthReport", () => useInternalApi<HealthReport>("admin/health")));
|
||||
}
|
||||
|
||||
export async function useActionLogs() {
|
||||
export async function useActionLogs(): Promise<Ref<PaginatedResult<LoggedAction> | null>> {
|
||||
return extract(await useAsyncData("useActionLogs", () => useInternalApi<PaginatedResult<LoggedAction>>("admin/log/")));
|
||||
}
|
||||
|
||||
export async function useVersionApprovals() {
|
||||
return await useAsyncData("useVersionApprovals", () =>
|
||||
useInternalApi<{ underReview: ReviewQueueEntry[]; notStarted: ReviewQueueEntry[] }>("admin/approval/versions")
|
||||
export async function useVersionApprovals(): Promise<Ref<{ underReview: ReviewQueueEntry[]; notStarted: ReviewQueueEntry[] } | null>> {
|
||||
return extract(
|
||||
await useAsyncData("useVersionApprovals", () =>
|
||||
useInternalApi<{ underReview: ReviewQueueEntry[]; notStarted: ReviewQueueEntry[] }>("admin/approval/versions")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function usePossibleOwners() {
|
||||
export async function usePossibleOwners(): Promise<Ref<ProjectOwner[] | null>> {
|
||||
return extract(await useAsyncData("usePossibleOwners", () => useInternalApi<ProjectOwner[]>("projects/possibleOwners")));
|
||||
}
|
||||
|
||||
export async function useOrgVisibility(user: string) {
|
||||
export async function useOrgVisibility(user: string): Promise<Ref<{ [key: string]: boolean } | null>> {
|
||||
return extract(
|
||||
await useAsyncData("useOrgVisibility:" + user, () => useInternalApi<{ [key: string]: boolean }>(`organizations/${user}/userOrganizationsVisibility`))
|
||||
);
|
||||
}
|
||||
|
||||
export async function useVersionInfo() {
|
||||
export async function useVersionInfo(): Promise<Ref<VersionInfo | null>> {
|
||||
return extract(await useAsyncData("useVersionInfo", () => useInternalApi<VersionInfo>(`data/version-info`)));
|
||||
}
|
||||
|
||||
export async function useUserData(user: string) {
|
||||
return await useAsyncData("useUserData:" + user, async () => {
|
||||
// noinspection ES6MissingAwait
|
||||
const data = await Promise.all([
|
||||
useApi<PaginatedResult<ProjectCompact>>(`users/${user}/starred`),
|
||||
useApi<PaginatedResult<ProjectCompact>>(`users/${user}/watching`),
|
||||
useApi<PaginatedResult<Project>>(`projects`, "get", {
|
||||
owner: user,
|
||||
}),
|
||||
useInternalApi<{ [key: string]: RoleTable }>(`organizations/${user}/userOrganizations`),
|
||||
useApi<ProjectCompact[]>(`users/${user}/pinned`),
|
||||
]);
|
||||
return {
|
||||
starred: data[0] as PaginatedResult<ProjectCompact>,
|
||||
watching: data[1] as PaginatedResult<ProjectCompact>,
|
||||
projects: data[2] as PaginatedResult<Project>,
|
||||
organizations: data[3] as { [key: string]: RoleTable },
|
||||
pinned: data[4] as ProjectCompact[],
|
||||
};
|
||||
});
|
||||
export async function useUserData(user: string): Promise<
|
||||
Ref<{
|
||||
starred: PaginatedResult<ProjectCompact>;
|
||||
watching: PaginatedResult<ProjectCompact>;
|
||||
projects: PaginatedResult<Project>;
|
||||
organizations: { [key: string]: RoleTable };
|
||||
pinned: ProjectCompact[];
|
||||
} | null>
|
||||
> {
|
||||
return extract(
|
||||
await useAsyncData("useUserData:" + user, async () => {
|
||||
// noinspection ES6MissingAwait
|
||||
const data = await Promise.all([
|
||||
useApi<PaginatedResult<ProjectCompact>>(`users/${user}/starred`),
|
||||
useApi<PaginatedResult<ProjectCompact>>(`users/${user}/watching`),
|
||||
useApi<PaginatedResult<Project>>(`projects`, "get", {
|
||||
owner: user,
|
||||
}),
|
||||
useInternalApi<{ [key: string]: RoleTable }>(`organizations/${user}/userOrganizations`),
|
||||
useApi<ProjectCompact[]>(`users/${user}/pinned`),
|
||||
]);
|
||||
return {
|
||||
starred: data[0] as PaginatedResult<ProjectCompact>,
|
||||
watching: data[1] as PaginatedResult<ProjectCompact>,
|
||||
projects: data[2] as PaginatedResult<Project>,
|
||||
organizations: data[3] as { [key: string]: RoleTable },
|
||||
pinned: data[4] as ProjectCompact[],
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function extract<T, E>(asyncData: AsyncData<T, E>) {
|
||||
function extract<T, E>(asyncData: AsyncData<T, E>): Ref<T | null> {
|
||||
if (asyncData.error?.value) {
|
||||
throw asyncData.error.value;
|
||||
handleRequestError(asyncData.error.value);
|
||||
return ref(null);
|
||||
} else {
|
||||
return asyncData.data;
|
||||
}
|
||||
|
@ -6,14 +6,13 @@ import { useNotificationStore } from "~/lib/store/notification";
|
||||
import { I18n } from "~/lib/i18n";
|
||||
import { createError } from "#imports";
|
||||
|
||||
export function handleRequestError(err: AxiosError, i18n: Composer = I18n.value, msg: string | undefined = undefined) {
|
||||
export function handleRequestError(err: AxiosError | unknown, i18n: Composer = I18n.value, msg: string | undefined = undefined) {
|
||||
if (import.meta.env.SSR) {
|
||||
_handleRequestError(err, i18n);
|
||||
return ref();
|
||||
}
|
||||
const notfication = useNotificationStore();
|
||||
const transformed = transformAxiosError(err);
|
||||
if (!err.isAxiosError) {
|
||||
if (!axios.isAxiosError(err)) {
|
||||
// everything should be an AxiosError
|
||||
console.log("no axios request error", transformed);
|
||||
notfication.error(transformed.message?.toString() || "Unknown error");
|
||||
@ -38,10 +37,9 @@ export function handleRequestError(err: AxiosError, i18n: Composer = I18n.value,
|
||||
console.log("unknown error", transformed);
|
||||
notfication.error(transformed.message?.toString() || "Unknown error");
|
||||
}
|
||||
return ref();
|
||||
}
|
||||
|
||||
function _handleRequestError(err: AxiosError, i18n: Composer) {
|
||||
function _handleRequestError(err: AxiosError | unknown, i18n: Composer) {
|
||||
function writeResponse(object: unknown) {
|
||||
console.log("writeResponse", object);
|
||||
// throw new Error("TODO: Implement me"); // TODO
|
||||
@ -49,7 +47,7 @@ function _handleRequestError(err: AxiosError, i18n: Composer) {
|
||||
}
|
||||
|
||||
const transformed = transformAxiosError(err);
|
||||
if (!err.isAxiosError) {
|
||||
if (!axios.isAxiosError(err)) {
|
||||
// everything should be an AxiosError
|
||||
writeResponse({
|
||||
status: 500,
|
||||
|
@ -8,7 +8,7 @@ import { usePage } from "~/composables/useApiHelper";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
|
||||
export async function useProjectPage(route: RouteLocationNormalizedLoaded, router: Router, i18n: ReturnType<typeof useI18n>, project: HangarProject) {
|
||||
const page = await usePage(route.params.user as string, route.params.project as string, route.params.all as string).catch((e) => handleRequestError(e));
|
||||
const page = await usePage(route.params.user as string, route.params.project as string, route.params.all as string);
|
||||
if (!page) {
|
||||
throw useErrorRedirect(route, 404, "Not found");
|
||||
}
|
||||
|
@ -2,17 +2,16 @@
|
||||
import { useRoute } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useOrganization, useUser } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const user = await useUser(route.params.user as string).catch((e) => handleRequestError(e));
|
||||
const user = await useUser(route.params.user as string);
|
||||
let organization = null;
|
||||
if (!user || !user.value) {
|
||||
throw useErrorRedirect(useRoute(), 404, "Not found");
|
||||
} else if (user.value?.isOrganization) {
|
||||
organization = await useOrganization(route.params.user as string).catch((e) => handleRequestError(e));
|
||||
organization = await useOrganization(route.params.user as string);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -5,7 +5,6 @@ import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { HangarProjectPage } from "hangar-internal";
|
||||
import { useProject } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
import ProjectHeader from "~/components/projects/ProjectHeader.vue";
|
||||
import ProjectNav from "~/components/projects/ProjectNav.vue";
|
||||
@ -19,7 +18,7 @@ defineProps({
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const project = await useProject(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e));
|
||||
const project = await useProject(route.params.user as string, route.params.project as string);
|
||||
if (!project || !project.value) {
|
||||
throw useErrorRedirect(route, 404, "Not found");
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const channels = await useProjectChannels(props.project.namespace.owner, props.project.namespace.slug).catch((e) => handleRequestError(e));
|
||||
const channels = await useProjectChannels(props.project.namespace.owner, props.project.namespace.slug);
|
||||
const validations = useBackendData.validations;
|
||||
const notifications = useNotificationStore();
|
||||
|
||||
@ -42,7 +42,7 @@ async function refreshChannels() {
|
||||
const newChannels = await useInternalApi<ProjectChannel[]>(`channels/${props.project.namespace.owner}/${props.project.namespace.slug}`).catch((e) =>
|
||||
handleRequestError(e)
|
||||
);
|
||||
if (channels && newChannels) {
|
||||
if (channels?.value && newChannels) {
|
||||
channels.value = newChannels;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import Link from "~/lib/components/design/Link.vue";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import Alert from "~/lib/components/design/Alert.vue";
|
||||
import { useProjectFlags } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { projectIconUrl } from "~/composables/useUrlHelper";
|
||||
import { definePageMeta } from "#imports";
|
||||
@ -24,7 +23,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const flags = await useProjectFlags(props.project.id).catch((e) => handleRequestError(e));
|
||||
const flags = await useProjectFlags(props.project.id);
|
||||
|
||||
const headers = [
|
||||
{ title: "Submitter", name: "user" },
|
||||
|
@ -28,7 +28,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const notes = await useProjectNotes(props.project.id).catch((e) => handleRequestError(e));
|
||||
const notes = await useProjectNotes(props.project.id);
|
||||
const text = ref("");
|
||||
const loading = ref(false);
|
||||
|
||||
@ -50,7 +50,7 @@ async function addNote() {
|
||||
}).catch((e) => handleRequestError(e));
|
||||
text.value = "";
|
||||
const newNotes = await useInternalApi<Note[]>("projects/notes/" + props.project.id).catch((e) => handleRequestError(e));
|
||||
if (notes && newNotes) {
|
||||
if (notes?.value && newNotes) {
|
||||
notes.value = newNotes;
|
||||
}
|
||||
loading.value = false;
|
||||
|
@ -3,7 +3,6 @@ import { useRoute } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
@ -15,7 +14,7 @@ import { useSeo } from "~/composables/useSeo";
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const stargazers = await useStargazers(route.params.user as string, route.params.project as string).catch<any>((e) => handleRequestError(e));
|
||||
const stargazers = await useStargazers(route.params.user as string, route.params.project as string);
|
||||
|
||||
const props = defineProps<{
|
||||
project: HangarProject;
|
||||
@ -37,8 +36,8 @@ useHead(
|
||||
<PageTitle>{{ i18n.t("project.stargazers") }}</PageTitle>
|
||||
</template>
|
||||
|
||||
<div v-if="stargazers.result && stargazers.result.length > 0" class="flex flex-wrap gap-4">
|
||||
<div v-for="stargazer in stargazers.result" :key="stargazer.name">
|
||||
<div v-if="stargazers?.result?.length > 0" class="flex flex-wrap gap-4">
|
||||
<div v-for="stargazer in stargazers?.result" :key="stargazer.name">
|
||||
<UserAvatar size="xs" :username="stargazer.name" :avatar-url="avatarUrl(stargazer.name)" />
|
||||
<Link :to="'/' + stargazer.name">{{ stargazer.name }}</Link>
|
||||
</div>
|
||||
|
@ -2,7 +2,6 @@
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useProjectVersionsInternal } from "~/composables/useApiHelper";
|
||||
import { useErrorRedirect } from "~/lib/composables/useErrorRedirect";
|
||||
import { Platform } from "~/types/enums";
|
||||
@ -14,10 +13,8 @@ const props = defineProps<{
|
||||
project: HangarProject;
|
||||
}>();
|
||||
|
||||
const version = await useProjectVersionsInternal(route.params.user as string, route.params.project as string, route.params.version as string).catch((e) =>
|
||||
handleRequestError(e)
|
||||
);
|
||||
if (!version || !version.value) {
|
||||
const version = await useProjectVersionsInternal(route.params.user as string, route.params.project as string, route.params.version as string);
|
||||
if (!version?.value) {
|
||||
throw useErrorRedirect(route, 404, "Not found");
|
||||
}
|
||||
|
||||
|
@ -48,10 +48,10 @@ const requestOptions = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const channels = await useProjectChannels(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e));
|
||||
const versions = await useProjectVersions(route.params.user as string, route.params.project as string).catch((e) => handleRequestError(e));
|
||||
const channels = await useProjectChannels(route.params.user as string, route.params.project as string);
|
||||
const versions = await useProjectVersions(route.params.user as string, route.params.project as string);
|
||||
|
||||
if (channels) {
|
||||
if (channels.value) {
|
||||
filter.channels.push(...(channels.value?.map((c) => c.name) || []));
|
||||
filter.platforms.push(...platforms.value.map((p) => p.enumName));
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { useRoute } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { HangarProject } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import UserAvatar from "~/components/UserAvatar.vue";
|
||||
@ -15,7 +14,7 @@ import { useSeo } from "~/composables/useSeo";
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const watchers = await useWatchers(route.params.user as string, route.params.project as string).catch<any>((e) => handleRequestError(e));
|
||||
const watchers = await useWatchers(route.params.user as string, route.params.project as string);
|
||||
|
||||
const props = defineProps<{
|
||||
project: HangarProject;
|
||||
@ -37,8 +36,8 @@ useHead(
|
||||
<PageTitle>{{ i18n.t("project.watchers") }}</PageTitle>
|
||||
</template>
|
||||
|
||||
<div v-if="watchers.result && watchers.result.length > 0" class="flex flex-wrap gap-4">
|
||||
<div v-for="watcher in watchers.result" :key="watcher.name">
|
||||
<div v-if="watchers?.result?.length > 0" class="flex flex-wrap gap-4">
|
||||
<div v-for="watcher in watchers?.result" :key="watcher.name">
|
||||
<UserAvatar size="xs" :username="watcher.name" :avatar-url="avatarUrl(watcher.name)" />
|
||||
<Link :to="'/' + watcher.name">{{ watcher.name }}</Link>
|
||||
</div>
|
||||
|
@ -37,7 +37,8 @@ const props = defineProps<{
|
||||
const i18n = useI18n();
|
||||
|
||||
const route = useRoute();
|
||||
const { starred, watching, projects, organizations, pinned } = (await useUserData(props.user.name)).value || {};
|
||||
const userData = await useUserData(props.user.name);
|
||||
const { starred, watching, projects, organizations, pinned } = userData.value || { starred: null };
|
||||
let organizationVisibility = null;
|
||||
if (props.user.name === useAuthStore().user?.name) {
|
||||
organizationVisibility = await useOrgVisibility(props.user.name);
|
||||
|
@ -6,7 +6,6 @@ import { useRoute } from "vue-router";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import { ReviewAction } from "~/types/enums";
|
||||
import { useVersionApprovals } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import Tag from "~/components/Tag.vue";
|
||||
@ -20,7 +19,7 @@ definePageMeta({
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const data = await useVersionApprovals().catch((e) => handleRequestError(e));
|
||||
const data = await useVersionApprovals();
|
||||
|
||||
const actions = {
|
||||
ongoing: [ReviewAction.START, ReviewAction.MESSAGE, ReviewAction.UNDO_APPROVAL, ReviewAction.REOPEN],
|
||||
|
@ -3,8 +3,6 @@ import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useUnresolvedFlags } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import Flags from "~/components/Flags.vue";
|
||||
@ -17,7 +15,6 @@ definePageMeta({
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const flags = await useUnresolvedFlags().catch((e) => handleRequestError(e));
|
||||
const loading = ref<{ [key: number]: boolean }>({});
|
||||
|
||||
const selectedTab = ref("unresolved");
|
||||
|
@ -3,7 +3,6 @@ import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useHealthReport } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
@ -16,7 +15,7 @@ definePageMeta({
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const healthReport = await useHealthReport().catch((e) => handleRequestError(e));
|
||||
const healthReport = await useHealthReport();
|
||||
|
||||
useHead(useSeo(i18n.t("health.title"), null, route, null));
|
||||
</script>
|
||||
|
@ -8,7 +8,6 @@ import { LoggedAction, LoggedActionType } from "hangar-internal";
|
||||
import { debounce } from "lodash-es";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useActionLogs } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import Link from "~/lib/components/design/Link.vue";
|
||||
@ -27,7 +26,7 @@ definePageMeta({
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const loggedActions = await useActionLogs().catch((e) => handleRequestError(e));
|
||||
const loggedActions = await useActionLogs();
|
||||
|
||||
// TODO add support for sorting
|
||||
const headers = [
|
||||
|
@ -15,7 +15,7 @@ import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { authUrl, forumUserUrl } from "~/composables/useUrlHelper";
|
||||
import { useUser } from "~/composables/useApiHelper";
|
||||
import { useProjects, useUser } from "~/composables/useApiHelper";
|
||||
import Tag from "~/components/Tag.vue";
|
||||
import InputSelect from "~/lib/components/ui/InputSelect.vue";
|
||||
import { useBackendData } from "~/store/backendData";
|
||||
@ -30,13 +30,11 @@ const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const projects = await useApi<PaginatedResult<Project>>("projects", "get", {
|
||||
owner: route.params.user,
|
||||
}).catch((e) => handleRequestError(e));
|
||||
const projects = await useProjects({ owner: route.params.user });
|
||||
const orgs = await useInternalApi<{ [key: string]: OrganizationRoleTable }>(`organizations/${route.params.user}/userOrganizations`).catch((e) =>
|
||||
handleRequestError(e)
|
||||
);
|
||||
const user = await useUser(route.params.user as string).catch((e) => handleRequestError(e));
|
||||
const user = await useUser(route.params.user as string);
|
||||
|
||||
const projectsConfig = [
|
||||
{ title: i18n.t("userAdmin.project"), name: "name" },
|
||||
@ -67,7 +65,7 @@ const selectedRole = ref();
|
||||
async function processRole(add: boolean) {
|
||||
try {
|
||||
await useInternalApi("/admin/user/" + route.params.user + "/" + selectedRole.value, add ? "POST" : "DELETE");
|
||||
if (user) {
|
||||
if (user?.value) {
|
||||
user.value = await useApi<User>(("users/" + route.params.user) as string);
|
||||
}
|
||||
} catch (e) {
|
||||
@ -90,7 +88,7 @@ useHead(useSeo(i18n.t("userAdmin.title") + " " + route.params.user, null, route,
|
||||
<Card class="basis-full md:basis-8/12">
|
||||
<template #header>{{ i18n.t("userAdmin.roles") }}</template>
|
||||
<div class="space-x-1">
|
||||
<Tag v-for="role in user.roles" :key="role.value" :color="{ background: role.color }" :name="role.title" />
|
||||
<Tag v-for="role in user?.roles" :key="role.value" :color="{ background: role.color }" :name="role.title" />
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2">
|
||||
@ -98,12 +96,12 @@ useHead(useSeo(i18n.t("userAdmin.title") + " " + route.params.user, null, route,
|
||||
<InputSelect v-model="selectedRole" :values="useBackendData.globalRoles" item-text="title" item-value="value"></InputSelect>
|
||||
</div>
|
||||
<div>
|
||||
<Button size="medium" :disabled="!selectedRole || user.roles.some((r) => r.value === selectedRole)" @click="processRole(true)">
|
||||
<Button size="medium" :disabled="!selectedRole || user?.roles.some((r) => r.value === selectedRole)" @click="processRole(true)">
|
||||
{{ i18n.t("general.add") }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<Button size="medium" :disabled="!selectedRole || !user.roles.some((r) => r.value === selectedRole)" @click="processRole(false)">
|
||||
<Button size="medium" :disabled="!selectedRole || !user?.roles.some((r) => r.value === selectedRole)" @click="processRole(false)">
|
||||
{{ i18n.t("general.delete") }}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -10,7 +10,7 @@ import Tag from "~/components/Tag.vue";
|
||||
import { useApi } from "~/composables/useApi";
|
||||
import { Header } from "~/components/SortableTable.vue";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { definePageMeta, handleRequestError, watch } from "#imports";
|
||||
import { definePageMeta, watch } from "#imports";
|
||||
import { useUsers } from "~/composables/useApiHelper";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
@ -33,7 +33,7 @@ const headers = [
|
||||
{ name: "org", title: i18n.t("pages.headers.organization"), sortable: true },
|
||||
] as Header[];
|
||||
|
||||
const users = await useUsers().catch((e) => handleRequestError(e));
|
||||
const users = await useUsers();
|
||||
const page = ref(0);
|
||||
const sort = ref<string[]>([]);
|
||||
const query = ref();
|
||||
|
@ -32,7 +32,7 @@ const requestParams = computed(() => {
|
||||
sort: sort.value,
|
||||
};
|
||||
});
|
||||
const authors = await useAuthors(requestParams.value).catch((e) => handleRequestError(e));
|
||||
const authors = await useAuthors(requestParams.value);
|
||||
|
||||
async function updateSort(col: string, sorter: Record<string, number>) {
|
||||
sort.value = [...Object.keys(sorter)]
|
||||
|
@ -9,7 +9,6 @@ import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import { useBackendData, useVisibleCategories, useVisiblePlatforms } from "~/store/backendData";
|
||||
import ProjectList from "~/components/projects/ProjectList.vue";
|
||||
import { useProjects } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import Card from "~/lib/components/design/Card.vue";
|
||||
import Container from "~/lib/components/design/Container.vue";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
@ -67,7 +66,7 @@ const requestParams = computed(() => {
|
||||
|
||||
return params;
|
||||
});
|
||||
const p = await useProjects(requestParams.value).catch((e) => handleRequestError(e));
|
||||
const p = await useProjects(requestParams.value);
|
||||
if (p && p.value) {
|
||||
projects.value = p.value;
|
||||
await checkOffsetLargerCount();
|
||||
@ -141,7 +140,7 @@ const script = {
|
||||
if (isRef(meta.script)) {
|
||||
meta.script.value.push(script);
|
||||
} else {
|
||||
meta.script = meta.script || [];
|
||||
meta.script = (meta.script || []) as [];
|
||||
meta.script.push(script);
|
||||
}
|
||||
useHead(meta);
|
||||
|
@ -30,11 +30,11 @@ const route = useRoute();
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
// TODO send in one
|
||||
const unreadNotifications = (await useUnreadNotifications().catch((e) => handleRequestError(e))) as Ref<PaginatedResult<HangarNotification>>;
|
||||
const readNotifications = (await useReadNotifications().catch((e) => handleRequestError(e))) as Ref<PaginatedResult<HangarNotification>>;
|
||||
const allNotifications = (await useNotifications().catch((e) => handleRequestError(e))) as Ref<PaginatedResult<HangarNotification>>;
|
||||
const notifications: Ref<PaginatedResult<HangarNotification>> = ref(unreadNotifications.value);
|
||||
const invites = (await useInvites().catch((e) => handleRequestError(e))) as Ref<Invites>;
|
||||
const unreadNotifications = await useUnreadNotifications();
|
||||
const readNotifications = await useReadNotifications();
|
||||
const allNotifications = await useNotifications();
|
||||
const notifications: Ref<PaginatedResult<HangarNotification> | null> = ref(unreadNotifications.value);
|
||||
const invites = await useInvites();
|
||||
|
||||
const selectedTab = ref("unread");
|
||||
const selectedTabs: Tab[] = [
|
||||
@ -66,6 +66,7 @@ useHead(useSeo("Notifications", null, route, null));
|
||||
|
||||
async function markAllAsRead() {
|
||||
await useInternalApi(`markallread`, "post").catch((e) => handleRequestError(e));
|
||||
if (!unreadNotifications.value) return;
|
||||
unreadNotifications.value.result = [];
|
||||
unreadNotifications.value.pagination.limit = 0;
|
||||
unreadNotifications.value.pagination.offset = 0;
|
||||
@ -75,6 +76,7 @@ async function markAllAsRead() {
|
||||
async function markNotificationRead(notification: HangarNotification, router = true) {
|
||||
await useInternalApi(`notifications/${notification.id}`, "post").catch((e) => handleRequestError(e));
|
||||
notification.read = true;
|
||||
if (!notifications.value) return;
|
||||
notifications.value.result = notifications.value.result.filter((n) => n !== notification);
|
||||
if (notification.action && router) {
|
||||
await useRouter().push(notification.action);
|
||||
@ -86,6 +88,7 @@ async function updateInvite(invite: Invite, status: "accept" | "decline") {
|
||||
if (status === "accept") {
|
||||
invite.accepted = true;
|
||||
} else {
|
||||
if (!invites.value) return;
|
||||
invites.value[invite.type] = invites.value[invite.type].filter((i) => i.roleTableId !== invite.roleTableId);
|
||||
}
|
||||
notificationStore.success(i18n.t(`notifications.invite.msgs.${status}`, [invite.name]));
|
||||
@ -112,11 +115,11 @@ function updateSelectedNotifications() {
|
||||
|
||||
<!-- Abuse tabs a little -->
|
||||
<Tabs v-model="selectedTab" :tabs="selectedTabs" :vertical="false" @click="updateSelectedNotifications()" />
|
||||
<div v-if="notifications.result.length === 0" class="text-lg">
|
||||
<div v-if="notifications?.result.length === 0" class="text-lg">
|
||||
{{ i18n.t(`notifications.empty.${selectedTab}`) }}
|
||||
</div>
|
||||
|
||||
<Pagination v-if="notifications.result.length !== 0" :items="notifications.result">
|
||||
<Pagination v-if="notifications?.result.length !== 0" :items="notifications?.result">
|
||||
<template #default="{ item }">
|
||||
<div class="p-1 mb-1 flex items-center">
|
||||
<div class="inline-flex items-center flex-grow">
|
||||
@ -139,7 +142,7 @@ function updateSelectedNotifications() {
|
||||
</div>
|
||||
</template>
|
||||
</Pagination>
|
||||
<Button v-if="notifications.result.length > 1 && selectedTab === 'unread'" size="small" @click="markAllAsRead">
|
||||
<Button v-if="notifications?.result.length > 1 && selectedTab === 'unread'" size="small" @click="markAllAsRead">
|
||||
{{ i18n.t("notifications.readAll") }}
|
||||
</Button>
|
||||
</Card>
|
||||
|
@ -34,7 +34,7 @@ const requestParams = computed(() => {
|
||||
sort: sort.value,
|
||||
};
|
||||
});
|
||||
const staff = await useStaff(requestParams.value).catch((e) => handleRequestError(e));
|
||||
const staff = await useStaff(requestParams.value);
|
||||
|
||||
async function updateSort(col: string, sorter: Record<string, number>) {
|
||||
sort.value = [...Object.keys(sorter)]
|
||||
|
Loading…
Reference in New Issue
Block a user