fix/admin-active-status #11

Merged
Edd merged 3 commits from fix/admin-active-status into develop 2026-01-22 18:56:28 +00:00
Showing only changes of commit 0d93b212c6 - Show all commits
+380 -60
View File
@@ -2,6 +2,269 @@
<div <div
class="min-h-screen bg-slate-50 dark:bg-slate-950 font-sans text-slate-900 dark:text-slate-200 selection:bg-blue-100 dark:selection:bg-blue-900 pb-12 transition-colors duration-300"> class="min-h-screen bg-slate-50 dark:bg-slate-950 font-sans text-slate-900 dark:text-slate-200 selection:bg-blue-100 dark:selection:bg-blue-900 pb-12 transition-colors duration-300">
<Transition enter-active-class="transition duration-200 ease-out" enter-from-class="opacity-0"
enter-to-class="opacity-100" leave-active-class="transition duration-150 ease-in"
leave-from-class="opacity-100" leave-to-class="opacity-0">
<div v-if="selectedPost" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 overflow-y-auto"
@click.self="selectedPost = null">
<div class="min-h-screen px-4 py-8 flex items-start justify-center">
<Transition enter-active-class="transition duration-200 ease-out"
enter-from-class="opacity-0 scale-95 translate-y-4"
enter-to-class="opacity-100 scale-100 translate-y-0"
leave-active-class="transition duration-150 ease-in"
leave-from-class="opacity-100 scale-100 translate-y-0"
leave-to-class="opacity-0 scale-95 translate-y-4">
<div v-if="selectedPost"
class="w-full max-w-4xl bg-white dark:bg-gray-900 rounded-2xl shadow-2xl border dark:border-gray-800"
@click.stop>
<!-- Close Button Header -->
<div
class="sticky top-0 z-10 flex justify-between items-center p-4 bg-white/95 dark:bg-gray-900/95 backdrop-blur-sm border-b dark:border-gray-800 rounded-t-2xl">
<button
class="flex items-center gap-2 text-sm font-bold text-blue-600 dark:text-blue-400 hover:underline"
@click="selectedPost = null">
BACK TO FEED
</button>
<button @click="selectedPost = null"
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Post Content - Same as main page -->
<article class="overflow-hidden">
<div
class="relative bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-800 dark:to-slate-900 p-8 border-b dark:border-gray-800">
<div
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-4">
<span
class="bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider shadow-sm border border-blue-200 dark:border-blue-800">
{{ selectedPost.type.replace('_', ' ') }}
</span>
<span
class="text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
{{ new Date(selectedPost.publicationDate ||
selectedPost.publication_date).toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
}) }}
</span>
</div>
<h1
class="text-3xl md:text-4xl font-extrabold text-gray-900 dark:text-white mb-6 leading-tight">
{{ selectedPost.title }}
</h1>
<div
class="grid grid-cols-1 md:grid-cols-2 gap-6 p-4 bg-white/50 dark:bg-gray-800/50 rounded-xl border border-gray-200 dark:border-gray-700 backdrop-blur-sm">
<div>
<h4 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">
Authors</h4>
<div class="flex flex-wrap gap-2">
<span v-for="(author, idx) in selectedPost.authors" :key="idx"
class="inline-flex items-center gap-1.5 text-sm font-semibold text-gray-700 dark:text-gray-200">
<span
class="w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 dark:bg-indigo-900 dark:text-indigo-300 flex items-center justify-center text-xs">
{{ author.charAt(0) }}
</span>
{{ author }}
</span>
</div>
</div>
<div v-if="selectedPost.submitter">
<h4 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">
Submitted By</h4>
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-full bg-blue-600 text-white flex items-center justify-center font-bold text-xs">
{{ selectedPost.submitter.name.charAt(0) }}
</div>
<div>
<div class="text-sm font-bold text-gray-900 dark:text-white">{{
selectedPost.submitter.name }}</div>
<div class="text-xs text-gray-500">{{ selectedPost.submitter.role }}
{{ selectedPost.submitter.email }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="p-8 max-h-[60vh] overflow-y-auto">
<div class="mb-10">
<h3
class="text-lg font-bold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<svg class="w-5 h-5 text-blue-500" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 12h16M4 18h7" />
</svg>
Abstract
</h3>
<p
class="text-gray-700 dark:text-gray-300 leading-relaxed text-lg text-justify whitespace-pre-line">
{{ selectedPost.abstractText || selectedPost.abstract }}
</p>
</div>
<div
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-6 mb-8 pb-8 border-b dark:border-gray-800">
<div v-if="selectedPost.tags && selectedPost.tags.length > 0">
<h4 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">
Tags</h4>
<div class="flex flex-wrap gap-2">
<span v-for="tag in selectedPost.tags" :key="tag.id"
class="px-3 py-1 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 text-sm font-medium border border-gray-200 dark:border-gray-700">
#{{ tag.name }}
</span>
</div>
</div>
<div v-else class="text-sm text-gray-400 italic">No tags assigned</div>
<div class="flex items-center gap-4">
<div
class="flex items-center bg-slate-100 dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-1.5 shadow-sm">
<button @click.stop="votePost(selectedPost, 1)"
class="p-2 rounded-lg transition-all active:scale-90"
:class="selectedPost.my_vote === 'UP' ? 'text-orange-600 bg-orange-100 dark:bg-orange-900/30' : 'text-slate-400 hover:text-orange-500 hover:bg-white dark:hover:bg-slate-700'"
title="Upvote">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M5 15l7-7 7 7" />
</svg>
</button>
<div class="min-w-[40px] text-center">
<span class="text-2xl font-black tracking-tight" :class="{
'text-orange-600': selectedPost.my_vote === 'UP',
'text-blue-600': selectedPost.my_vote === 'DOWN',
'text-slate-900 dark:text-white': !selectedPost.my_vote || selectedPost.my_vote === 'NONE'
}">
{{ selectedPost.rating }}
</span>
</div>
<button @click.stop="votePost(selectedPost, -1)"
class="p-2 rounded-lg transition-all active:scale-90"
:class="selectedPost.my_vote === 'DOWN' ? 'text-blue-600 bg-blue-100 dark:bg-blue-900/30' : 'text-slate-400 hover:text-blue-500 hover:bg-white dark:hover:bg-slate-700'"
title="Downvote">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
</div>
</div>
<div v-if="selectedPost.attachedFile || selectedPost.attached_file"
class="mb-10 bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-xl p-5 hover:border-blue-400 transition-colors group">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div
class="p-3 bg-red-100 text-red-600 rounded-lg group-hover:scale-110 transition-transform">
<svg class="w-8 h-8" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
</div>
<div>
<div class="font-bold text-gray-900 dark:text-white mb-0.5">
Attachment Available</div>
<div
class="text-xs text-gray-500 font-mono truncate max-w-[200px] md:max-w-md">
{{ getFileName(selectedPost.attachedFile ||
selectedPost.attached_file) }}
</div>
</div>
</div>
<button @click.stop="downloadFile(selectedPost)"
class="flex items-center gap-2 px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-lg shadow-lg shadow-blue-500/30 transform hover:-translate-y-0.5 transition-all">
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Download
</button>
</div>
</div>
<hr class="border-gray-100 dark:border-gray-800 mb-8">
<section>
<h3 class="text-xl font-bold mb-6 flex items-center gap-2">
Comments
<span
class="bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 text-xs px-2 py-1 rounded-full">
{{ selectedPost.comments?.length || 0 }}
</span>
</h3>
<div class="space-y-3">
<textarea v-model="newCommentContent" rows="3"
class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-all text-sm"
placeholder="What are your thoughts?" />
<div class="flex justify-end">
<button :disabled="!newCommentContent.trim()"
class="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white font-bold px-6 py-2 rounded-lg shadow transition-all active:scale-95"
@click="submitComment">
COMMENT
</button>
</div>
</div>
<div class="mt-8 space-y-6">
<div v-if="!selectedPost.comments || selectedPost.comments.length === 0"
class="text-center py-8 text-gray-400 italic">
Be the first to comment!
</div>
<div v-for="comment in selectedPost.comments" :key="comment.id"
class="flex gap-4">
<div
class="w-8 h-8 rounded-full bg-gradient-to-br from-gray-200 to-gray-300 dark:from-gray-700 dark:to-gray-600 flex-shrink-0" />
<div class="flex-1">
<div
class="bg-gray-50 dark:bg-gray-800/50 p-4 rounded-xl rounded-tl-none border border-gray-100 dark:border-gray-800">
<div class="flex items-center justify-between mb-2">
<span
class="font-bold text-sm text-blue-600 dark:text-blue-400">{{
comment.author.name }}</span>
<span class="text-[10px] text-gray-400 uppercase">{{ new
Date(comment.created_at).toLocaleDateString() }}</span>
</div>
<p
class="text-gray-700 dark:text-gray-300 text-sm leading-relaxed">
{{ comment.content }}
</p>
</div>
</div>
</div>
</div>
</section>
</div>
</article>
</div>
</Transition>
</div>
</div>
</Transition>
<div class="container mx-auto max-w-7xl px-4 py-8 md:py-12"> <div class="container mx-auto max-w-7xl px-4 py-8 md:py-12">
<div class="grid grid-cols-1 md:grid-cols-12 gap-8 lg:gap-12"> <div class="grid grid-cols-1 md:grid-cols-12 gap-8 lg:gap-12">
@@ -119,11 +382,13 @@
leave-from-class="transform opacity-100 translate-y-0" leave-from-class="transform opacity-100 translate-y-0"
leave-to-class="transform opacity-0 translate-y-2"> leave-to-class="transform opacity-0 translate-y-2">
<div v-if="activeTab === 'overview'" key="overview" class="space-y-6"> <div v-if="activeTab === 'overview'" key="overview" class="space-y-6">
<div <div
class="bg-white dark:bg-slate-900 p-6 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-800 transition-colors"> class="bg-white dark:bg-slate-900 p-6 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-800 transition-colors">
<h2 <h2
class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3"> class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3">
Biography</h2> Biography
</h2>
<p class="text-slate-700 dark:text-slate-300 leading-relaxed text-sm"> <p class="text-slate-700 dark:text-slate-300 leading-relaxed text-sm">
{{ userProfile.biography }} {{ userProfile.biography }}
</p> </p>
@@ -134,13 +399,12 @@
Recent Publications Recent Publications
<span <span
class="bg-slate-200 dark:bg-slate-800 text-slate-700 dark:text-slate-300 text-xs font-bold px-2 py-0.5 rounded-full"> class="bg-slate-200 dark:bg-slate-800 text-slate-700 dark:text-slate-300 text-xs font-bold px-2 py-0.5 rounded-full">
{{ userProfile.publications.length }} {{ userProfile.publications.length }} Papers
</span> </span>
</h2> </h2>
</div> </div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div <div
class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4"> class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
<div class="flex flex-wrap items-center gap-2 w-full sm:w-auto"> <div class="flex flex-wrap items-center gap-2 w-full sm:w-auto">
@@ -175,90 +439,60 @@
</div> </div>
</div> </div>
<div v-if="userProfile.publications.length === 0" <div v-if="userProfile.publications.length === 0" class="...">
class="relative overflow-hidden rounded-2xl border-2 border-dashed border-slate-300 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50 p-12 text-center hover:border-blue-400 dark:hover:border-blue-500 transition-all duration-300 group">
<div <div
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-48 h-48 bg-blue-500/10 dark:bg-blue-400/10 rounded-full blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none" /> class="relative overflow-hidden rounded-2xl border-2 border-dashed border-slate-300 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50 p-12 text-center hover:border-blue-400 dark:hover:border-blue-500 transition-all duration-300 group">
<div class="relative z-10 flex flex-col items-center">
<div class="relative z-10 flex flex-col items-center"> <h3 class="text-lg font-bold text-slate-900 dark:text-white mb-1">No
<div publications yet</h3>
class="mb-4 p-4 bg-white dark:bg-slate-800 rounded-full shadow-sm ring-1 ring-slate-100 dark:ring-slate-700 group-hover:scale-110 group-hover:ring-blue-200 dark:group-hover:ring-blue-800 transition-all duration-300"> <button @click="goToCreatePost()"
<svg class="w-8 h-8 text-blue-500 dark:text-blue-400" fill="none" class="mt-4 inline-flex items-center gap-2 px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl shadow-lg transition-all cursor-pointer">
viewBox="0 0 24 24" stroke="currentColor"> Submit your first paper
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" </button>
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div> </div>
<h3 class="text-lg font-bold text-slate-900 dark:text-white mb-1">
No publications yet
</h3>
<p
class="text-slate-500 dark:text-slate-400 text-sm max-w-xs mx-auto mb-6 leading-relaxed">
Your profile is looking a little empty. Share your research with the
community to start building your portfolio.
</p>
<button
class="inline-flex items-center gap-2 px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-xl shadow-lg shadow-blue-500/20 hover:shadow-blue-500/30 transform active:scale-95 transition-all duration-200 cursor-pointer"
@click="goToCreatePost()">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 4v16m8-8H4" />
</svg>
Submit your first paper
</button>
</div> </div>
</div> </div>
<div v-for="post in userProfile.publicationsData" :key="post.id" <div v-for="post in userProfile.publications" :key="post.id"
class="group bg-white dark:bg-gray-900 border dark:border-gray-800 rounded-xl p-5 shadow-sm hover:border-blue-500/50 transition-all cursor-pointer mb-4" class="group bg-white dark:bg-gray-900 border dark:border-gray-800 rounded-xl p-5 shadow-sm hover:border-blue-500/50 transition-all cursor-pointer mb-4"
@click="fetchPostDetail(post.id)"> @click="fetchPostDetail(post.id)">
<div class="flex gap-4"> <div class="flex gap-4">
<div <div
class="flex flex-col items-center gap-1 text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800/50 p-2 rounded-lg h-fit"> class="flex flex-col items-center gap-1 text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800/50 p-2 rounded-lg h-fit">
<button class="hover:text-blue-500 transition-colors text-lg" <button class="hover:text-blue-500 transition-colors text-lg"
:class="{ 'text-orange-500': post.my_vote === 'UP' }" :class="{ 'text-orange-500': post.my_vote === 'UP' }"
@click.stop="votePost(post, 1)"></button> @click.stop="votePost(post, 1)"></button>
<span class="text-xs font-bold">{{ post.rating }}</span> <span class="text-xs font-bold">{{ post.rating }}</span>
<button class="hover:text-red-500 transition-colors text-lg" <button class="hover:text-red-500 transition-colors text-lg"
:class="{ 'text-blue-500': post.my_vote === 'DOWN' }" :class="{ 'text-blue-500': post.my_vote === 'DOWN' }"
@click.stop="votePost(post, -1)"></button> @click.stop="votePost(post, -1)"></button>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center gap-2 mb-1"> <div class="flex items-center gap-2 mb-1">
<span <span
class="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400"> class="text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400">
{{ post.type.replace('_', ' ') }} {{ post.type.replace('_', ' ') }}
</span> </span>
<span class="text-xs text-gray-500">{{ post.publication_date <span class="text-xs text-gray-500">{{ post.publishedDate }}</span>
}}</span>
</div> </div>
<h2 class="text-lg font-bold text-gray-800 dark:text-white mb-2">{{ <h2 class="text-lg font-bold text-gray-800 dark:text-white mb-2">{{
post.title }} post.title }}</h2>
</h2> <p class="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">By {{
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">By post.authors.join(', ') }}</p>
{{
post.authors.join(', ') }}
</p>
<div <div
class="flex items-center gap-4 text-xs font-bold text-gray-500 uppercase"> class="flex items-center gap-4 text-xs font-bold text-gray-500 uppercase">
<span>💬 {{ post.comment_count }} Comments</span> <span>💬 {{ post.comment_count }} Comments</span>
<div class="flex gap-2"> <div class="flex gap-2">
<span v-for="tag in post.tags" :key="tag.id" <span v-for="tag in post.tags" :key="tag.id"
class="text-blue-600 dark:text-blue-400">#{{ tag.name class="text-blue-600 dark:text-blue-400">#{{ tag.name }}</span>
}}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-else-if="activeTab === 'activity'" key="activity" class="space-y-4"> <div v-else-if="activeTab === 'activity'" key="activity" class="space-y-4">
@@ -322,7 +556,6 @@
</p> </p>
</div> </div>
</Transition> </Transition>
</div> </div>
</div> </div>
</div> </div>
@@ -351,6 +584,8 @@ const filterType = ref('ALL'); // 'ALL' | 'CONFERENCE_PAPER' | 'RESEARCH_POST'
const page = ref(1); const page = ref(1);
// --- Interfaces --- // --- Interfaces ---
const selectedPost = ref(null);
const newCommentContent = ref('');
interface Tag { interface Tag {
id: number; id: number;
@@ -401,6 +636,28 @@ const userProfile = ref({
}); });
// --- API Actions --- // --- API Actions ---
const getFileName = (fullPath: string) => {
if (!fullPath) return null;
return fullPath.split(/[/\\]/).pop();
};
const downloadFile = async (post: any) => {
if (!post.attachedFile) return;
const filename = getFileName(post.attachedFile);
const downloadUrl = `${api}/publications/download/${filename}`; // Verifica se este endpoint existe no teu backend
try {
// Método simples para download (se o endpoint for público ou lidar com cookies)
const link = document.createElement('a');
link.href = downloadUrl;
link.setAttribute('download', filename || 'download');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (e) {
console.error("Download error:", e);
}
};
// Fetch Tags (EP23) - To populate the select box // Fetch Tags (EP23) - To populate the select box
async function fetchTags() { async function fetchTags() {
@@ -416,13 +673,17 @@ async function fetchTags() {
} }
} }
const fetchPostDetail = async (id) => { const fetchPostDetail = async (id: number) => {
loading.value = true; loading.value = true;
try { try {
const postDetail = await $fetch(`${api}/publications/${id}`, { const postDetail = await $fetch(`${api}/publications/${id}`, {
headers: { Authorization: `Bearer ${token.value}` } headers: { Authorization: `Bearer ${token.value}` }
}); });
selectedPost.value = postDetail; selectedPost.value = postDetail;
window.scrollTo({ top: 0, behavior: 'smooth' });
} catch (err) { } catch (err) {
console.error("Failed to fetch post detail:", err); console.error("Failed to fetch post detail:", err);
} finally { } finally {
@@ -466,13 +727,10 @@ async function fetchProfileData() {
loading.value = true; loading.value = true;
try { try {
// Fetch User Details
const userData = await $fetch(`${api}/users/me`, { const userData = await $fetch(`${api}/users/me`, {
method: 'GET', method: 'GET',
headers: { Authorization: `Bearer ${token.value}` } headers: { Authorization: `Bearer ${token.value}` }
}); });
// Fetch User Publications
const publicationsData = await $fetch(`${api}/publications/me`, { const publicationsData = await $fetch(`${api}/publications/me`, {
method: 'GET', method: 'GET',
headers: { Authorization: `Bearer ${token.value}` }, headers: { Authorization: `Bearer ${token.value}` },
@@ -484,7 +742,6 @@ async function fetchProfileData() {
} }
}); });
// Map Data
userProfile.value = { userProfile.value = {
...userProfile.value, ...userProfile.value,
id: userData.id, id: userData.id,
@@ -497,14 +754,18 @@ async function fetchProfileData() {
userProfile.value.publications = (publicationsData as any[]).map(pub => { userProfile.value.publications = (publicationsData as any[]).map(pub => {
const pubDate = new Date(pub.publication_date); const pubDate = new Date(pub.publication_date);
return { return {
id: pub.id, ...pub, // Manter todos os campos originais da API
title: pub.title,
abstract: pub.abstract || 'No abstract available.',
publishedDate: pubDate.toLocaleDateString(), publishedDate: pubDate.toLocaleDateString(),
publishedYear: pubDate.getFullYear().toString(), publishedYear: pubDate.getFullYear().toString(),
citations: pub.rating || 0, citations: pub.rating || 0,
field: pub.tags && pub.tags.length > 0 ? pub.tags[0].name : 'General'
authors: pub.authors || [],
tags: pub.tags || [],
comment_count: pub.comment_count || 0,
rating: pub.rating || 0
}; };
}); });
@@ -515,6 +776,39 @@ async function fetchProfileData() {
} }
} }
const votePost = async (targetPost, direction) => {
if (!targetPost) return;
let voteTypeToSend = 'NONE';
const currentVote = targetPost.my_vote || 'NONE';
if (direction === 1) {
voteTypeToSend = currentVote === 'UP' ? 'NONE' : 'UP';
} else if (direction === -1) {
voteTypeToSend = currentVote === 'DOWN' ? 'NONE' : 'DOWN';
}
try {
const response = await $fetch(`${api}/publications/${targetPost.id}/vote`, {
method: 'PUT',
headers: { Authorization: `Bearer ${token.value}` },
body: { vote_type: voteTypeToSend }
});
targetPost.rating = response.score;
targetPost.my_vote = response.my_vote;
if (selectedPost.value && selectedPost.value.id === targetPost.id) {
selectedPost.value.rating = response.score;
selectedPost.value.my_vote = response.my_vote;
}
} catch (error) {
console.error("Vote failed", error);
alert("Failed to submit vote.");
}
};
// --- Redirects --- // --- Redirects ---
const goToCreatePost = () => { const goToCreatePost = () => {
console.log("Navigating to create post modal..."); console.log("Navigating to create post modal...");
@@ -530,3 +824,29 @@ onMounted(() => {
fetchActivityHistory(); // Fetch history on mount fetchActivityHistory(); // Fetch history on mount
}); });
</script> </script>
<style scoped>
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fadeInUp 0.5s ease-out forwards;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>