Add user profile page (#302)

* Add user page and API endpoint

* add: /user/[id] page
* add: /api/v1/user/[id] API endpoint

* Change loading message in user profile page

* Fix build errors, prettier code
This commit is contained in:
Andus
2026-01-04 03:45:20 +01:00
committed by GitHub
parent c001a8c808
commit 8f5d8a43c5
3 changed files with 112 additions and 0 deletions

View File

@@ -629,6 +629,14 @@
"type": "Type", "type": "Type",
"upload": "Upload", "upload": "Upload",
"uploadFile": "Upload file", "uploadFile": "Upload file",
"user": {
"unknown": "Unknown user",
"editProfile": "Edit profile",
"recent": "Recent activity (TODO)",
"recentSub": "Recent activity by this user",
"notFound": "User not found",
"noActivity": "No recent activity"
},
"userHeader": { "userHeader": {
"closeSidebar": "Close sidebar", "closeSidebar": "Close sidebar",
"links": { "links": {

75
pages/user/[id]/index.vue Normal file
View File

@@ -0,0 +1,75 @@
<template>
<div class="max-w-6xl mx-auto px-4 py-10">
<div class="flex items-center gap-x-6">
<img
v-if="profile?.profilePictureObjectId"
:src="useObject(profile.profilePictureObjectId)"
class="w-24 h-24 rounded-md object-cover"
/>
<div>
<h1 class="text-2xl font-bold font-display text-zinc-100">
{{ profile?.displayName ?? profile?.username ?? $t("user.unknown") }}
</h1>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<div class="text-zinc-400 mt-1">@{{ profile?.username }}</div>
<div class="mt-3">
<NuxtLink
v-if="isCurrentUser"
to="/account"
class="px-3 py-2 bg-zinc-800 rounded text-sm text-zinc-200 hover:bg-zinc-700"
>
{{ $t("user.editProfile") }}
</NuxtLink>
</div>
</div>
</div>
<div class="mt-10">
<h2 class="text-xl font-semibold font-display text-zinc-100">
{{ $t("user.recent") }}
</h2>
<p class="mt-2 text-zinc-400">{{ $t("user.recentSub") }}</p>
<div class="mt-6">
<div v-if="loading" class="text-zinc-500">
{{ $t("common.srLoading") }}
</div>
<div v-else-if="!profile">
<div class="text-zinc-400">{{ $t("user.notFound") }}</div>
</div>
<div v-else>
<div class="mt-4 text-zinc-400">{{ $t("user.noActivity") }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useObject } from "~/composables/objects";
import { useUser } from "~/composables/user";
import type { UserModel } from "~/prisma/client/models";
const route = useRoute();
const id = (route.params.id ?? "") as string;
const loading = ref(true);
let profile: UserModel | null = null;
try {
profile = await $dropFetch(`/api/v1/user/${id}`);
} catch {
profile = null;
} finally {
loading.value = false;
}
const current = useUser();
const isCurrentUser = computed(
() => !!current.value && current.value.id === profile?.id,
);
useHead({
title: profile?.displayName ?? profile?.username ?? "User",
});
</script>

View File

@@ -0,0 +1,29 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const requestingUser = await aclManager.getUserACL(h3, ["read"]);
if (!requestingUser) throw createError({ statusCode: 403 });
const userId = getRouterParam(h3, "id");
if (!userId)
throw createError({
statusCode: 400,
statusMessage: "No userId in route.",
});
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
username: true,
displayName: true,
profilePictureObjectId: true,
},
});
if (!user)
throw createError({ statusCode: 404, statusMessage: "User not found." });
return user;
});