mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-02-05 14:40:33 +08:00
start with user page
This commit is contained in:
parent
44a4015eed
commit
caea626d30
@ -1,7 +1,18 @@
|
|||||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||||
import { PaginatedResult, Project, User } from "hangar-api";
|
import { PaginatedResult, Project, ProjectCompact, Role, User } from "hangar-api";
|
||||||
import { useInitialState } from "~/composables/useInitialState";
|
import { useInitialState } from "~/composables/useInitialState";
|
||||||
import { Flag, HangarNotification, HangarProject, HealthReport, Invites, LoggedAction, Note, ProjectChannel, ReviewQueueEntry } from "hangar-internal";
|
import {
|
||||||
|
Flag,
|
||||||
|
HangarNotification,
|
||||||
|
HangarProject,
|
||||||
|
HealthReport,
|
||||||
|
Invites,
|
||||||
|
LoggedAction,
|
||||||
|
Note,
|
||||||
|
ProjectChannel,
|
||||||
|
ReviewQueueEntry,
|
||||||
|
RoleTable,
|
||||||
|
} from "hangar-internal";
|
||||||
|
|
||||||
export async function useProjects(pagination = { limit: 25, offset: 0 }, blocking = true) {
|
export async function useProjects(pagination = { limit: 25, offset: 0 }, blocking = true) {
|
||||||
return useInitialState("useProjects", () => useApi<PaginatedResult<Project>>("projects", false, "get", pagination), blocking);
|
return useInitialState("useProjects", () => useApi<PaginatedResult<Project>>("projects", false, "get", pagination), blocking);
|
||||||
@ -70,3 +81,35 @@ export async function useVersionApprovals(blocking = true) {
|
|||||||
blocking
|
blocking
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function useOrgVisibility(user: string, blocking = true) {
|
||||||
|
return useInitialState(
|
||||||
|
"useOrgVisibility",
|
||||||
|
() => useInternalApi<{ [key: string]: boolean }>(`organizations/${user}/userOrganizationsVisibility`, true),
|
||||||
|
blocking
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function useUserData(user: string, blocking = true) {
|
||||||
|
return useInitialState(
|
||||||
|
"useUserData",
|
||||||
|
async () => {
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
|
const data = await Promise.all([
|
||||||
|
useApi<PaginatedResult<ProjectCompact>>(`users/${user}/starred`, false),
|
||||||
|
useApi<PaginatedResult<ProjectCompact>>(`users/${user}/watching`, false),
|
||||||
|
useApi<PaginatedResult<Project>>(`projects`, false, "get", {
|
||||||
|
owner: user,
|
||||||
|
}),
|
||||||
|
useInternalApi<{ [key: string]: RoleTable }>(`organizations/${user}/userOrganizations`, false),
|
||||||
|
] as Promise<any>[]);
|
||||||
|
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 },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
blocking
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -10,10 +10,13 @@ const ctx = useContext();
|
|||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const user = await useUser(useRoute().params.user as string).catch((e) => handleRequestError(e, ctx, i18n));
|
const user = await useUser(useRoute().params.user as string).catch((e) => handleRequestError(e, ctx, i18n));
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
// TODO check if user is an org here
|
||||||
await useRouter().push(useErrorRedirect(useRoute(), 404, "Not found"));
|
await useRouter().push(useErrorRedirect(useRoute(), 404, "Not found"));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<router-view v-if="user" :user="user"></router-view>
|
<router-view v-if="user" v-slot="{ Component }">
|
||||||
|
<component :is="Component" :user="user" :dum="user" />
|
||||||
|
</router-view>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1>version</h1>
|
<h1>version</h1>
|
||||||
|
<router-view></router-view>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,18 +1,99 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PropType } from "vue";
|
|
||||||
import { User } from "hangar-api";
|
import { User } from "hangar-api";
|
||||||
|
import ProjectList from "~/components/ProjectList.vue";
|
||||||
|
import Card from "~/components/design/Card.vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { avatarUrl } from "~/composables/useUrlHelper";
|
||||||
|
import UserAvatar from "~/components/UserAvatar.vue";
|
||||||
|
import Link from "~/components/design/Link.vue";
|
||||||
|
import MemberList from "~/components/projects/MemberList.vue";
|
||||||
|
import { useContext } from "vite-ssr/vue";
|
||||||
|
import { useOrgVisibility, useUserData } from "~/composables/useApiHelper";
|
||||||
|
import { useBackendDataStore } from "~/store/backendData";
|
||||||
|
import { useAuthStore } from "~/store/auth";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps<{
|
||||||
user: {
|
user: User;
|
||||||
type: Object as PropType<User>,
|
}>();
|
||||||
required: true,
|
const i18n = useI18n();
|
||||||
},
|
const ctx = useContext();
|
||||||
});
|
const route = useRoute();
|
||||||
|
|
||||||
|
const { starred, watching, projects, organizations } = (await useUserData(props.user.name)).value || {};
|
||||||
|
let organizationVisibility = null;
|
||||||
|
if (props.user.name === useAuthStore().user?.name) {
|
||||||
|
organizationVisibility = await useOrgVisibility(props.user.name);
|
||||||
|
}
|
||||||
|
const orgRoles = useBackendDataStore().orgRoles;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<!-- todo user header -->
|
||||||
user child
|
<div class="flex gap-4">
|
||||||
{{ user }}
|
<div class="flex-basis-full md:flex-basis-8/12 flex-grow">
|
||||||
|
<ProjectList :projects="projects"></ProjectList>
|
||||||
|
</div>
|
||||||
|
<div class="flex-basis-full md:flex-basis-4/12 flex-grow">
|
||||||
|
<template v-if="!user.isOrganization">
|
||||||
|
<Card class="mb-4">
|
||||||
|
<template #header>
|
||||||
|
{{ i18n.t("author.orgs") }}
|
||||||
|
<!-- todo org visibility modal -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="(orgRole, orgName) in organizations" :key="orgName">
|
||||||
|
<router-link :to="orgName" class="flex">
|
||||||
|
<UserAvatar :username="orgName" :avatar-url="avatarUrl(orgName)" size="xs" />
|
||||||
|
|
||||||
|
{{ orgName }}
|
||||||
|
|
||||||
|
{{ orgRole.role.title }}
|
||||||
|
|
||||||
|
<span v-if="organizationVisibility && organizationVisibility[orgName]"> Hidden </span>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<span v-if="!organizations || Object.keys(organizations).length === 0">
|
||||||
|
{{ i18n.t("author.noOrgs", [props.user.name]) }}
|
||||||
|
</span>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card class="mb-4">
|
||||||
|
<template #header>{{ i18n.t("author.stars") }}</template>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="star in starred.result" :key="star.name">
|
||||||
|
<Link :to="'/' + star.namespace.owner + '/' + star.namespace.slug">
|
||||||
|
{{ star.namespace.owner }}/<strong>{{ star.name }}</strong>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<span v-if="!starred || starred.result.length === 0">
|
||||||
|
{{ i18n.t("author.noStarred", [props.user.name]) }}
|
||||||
|
</span>
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<template #header>{{ i18n.t("author.watching") }}</template>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="watch in watching.result" :key="watch.name">
|
||||||
|
<Link :to="'/' + watch.namespace.owner + '/' + watch.namespace.slug"
|
||||||
|
>{{ watch.namespace.owner }}/<strong>{{ watch.name }}</strong></Link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<span v-if="!watching || watching.result.length === 0">
|
||||||
|
{{ i18n.t("author.noStarred", [props.user.name]) }}
|
||||||
|
</span>
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
<MemberList v-else :members="organization.members" :roles="orgRoles" :org="true" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -4,7 +4,7 @@ import { computed, ref } from "vue";
|
|||||||
import { IPlatform, IProjectCategory, IPrompt, IVisibility } from "hangar-internal";
|
import { IPlatform, IProjectCategory, IPrompt, IVisibility } from "hangar-internal";
|
||||||
import { NamedPermission, Platform, ProjectCategory, Prompt } from "~/types/enums";
|
import { NamedPermission, Platform, ProjectCategory, Prompt } from "~/types/enums";
|
||||||
|
|
||||||
import { Announcement as AnnouncementObject, Announcement, IPermission } from "hangar-api";
|
import { Announcement as AnnouncementObject, Announcement, IPermission, Role } from "hangar-api";
|
||||||
import { fetchIfNeeded, useInternalApi } from "~/composables/useApi";
|
import { fetchIfNeeded, useInternalApi } from "~/composables/useApi";
|
||||||
|
|
||||||
interface Validation {
|
interface Validation {
|
||||||
@ -40,6 +40,7 @@ export const useBackendDataStore = defineStore("backendData", () => {
|
|||||||
const announcements = ref<Announcement[]>([]);
|
const announcements = ref<Announcement[]>([]);
|
||||||
const visibilities = ref<IVisibility[]>([]);
|
const visibilities = ref<IVisibility[]>([]);
|
||||||
const licenses = ref<string[]>([]);
|
const licenses = ref<string[]>([]);
|
||||||
|
const orgRoles = ref<Role[]>([]);
|
||||||
|
|
||||||
async function initBackendData() {
|
async function initBackendData() {
|
||||||
try {
|
try {
|
||||||
@ -79,6 +80,8 @@ export const useBackendDataStore = defineStore("backendData", () => {
|
|||||||
await fetchIfNeeded(async () => await useInternalApi<IVisibility[]>("data/visibilities", false), visibilities);
|
await fetchIfNeeded(async () => await useInternalApi<IVisibility[]>("data/visibilities", false), visibilities);
|
||||||
|
|
||||||
await fetchIfNeeded(async () => await useInternalApi("data/validations", false), validations);
|
await fetchIfNeeded(async () => await useInternalApi("data/validations", false), validations);
|
||||||
|
|
||||||
|
await fetchIfNeeded(async () => await useInternalApi("data/orgRoles", false), validations);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("ERROR FETCHING BACKEND DATA");
|
console.error("ERROR FETCHING BACKEND DATA");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@ -97,6 +100,7 @@ export const useBackendDataStore = defineStore("backendData", () => {
|
|||||||
licenses,
|
licenses,
|
||||||
announcements,
|
announcements,
|
||||||
visibilities,
|
visibilities,
|
||||||
|
orgRoles,
|
||||||
initBackendData,
|
initBackendData,
|
||||||
visibleCategories,
|
visibleCategories,
|
||||||
visiblePlatforms,
|
visiblePlatforms,
|
||||||
|
Loading…
Reference in New Issue
Block a user