feat: Profile Info Fetch. Admin Panel. Profile Settings. Profile Log. Fixed Navbar. Fixed Routing. Fixed Overall desgin flaws
This commit is contained in:
+338
-137
@@ -1,61 +1,65 @@
|
||||
<template>
|
||||
<title> Profile - {{user.name}}</title>
|
||||
<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">
|
||||
<title>{{ userProfile.name }} - Reddit for Academic Papers</title>
|
||||
<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">
|
||||
|
||||
<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="md:col-span-4 lg:col-span-3 flex flex-col gap-6">
|
||||
|
||||
<div class="bg-white dark:bg-slate-900 rounded-2xl p-6 shadow-sm border border-slate-200 dark:border-slate-800 transition-colors duration-300">
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900 rounded-2xl p-6 shadow-sm border border-slate-200 dark:border-slate-800 transition-colors duration-300">
|
||||
<div class="flex flex-col items-center text-center">
|
||||
<div class="relative inline-block mb-4">
|
||||
<div
|
||||
class="h-32 w-32 rounded-full border-4 border-white dark:border-slate-800 bg-slate-100 dark:bg-slate-800 overflow-hidden shadow-md ring-1 ring-slate-200 dark:ring-slate-700">
|
||||
<img :src="user.avatarUrl" :alt="user.name"
|
||||
<img :src="userProfile.avatarUrl" :alt="userProfile.name"
|
||||
class="h-full w-full object-cover transition-transform hover:scale-105" />
|
||||
</div>
|
||||
<div class="absolute bottom-2 right-2 h-5 w-5 rounded-full border-[3px] border-white dark:border-slate-900 shadow-sm"
|
||||
:class="user.isActive ? 'bg-emerald-500' : 'bg-gray-400'" title="Online Status">
|
||||
:class="userProfile.is_active ? 'bg-emerald-500' : 'bg-gray-400'"
|
||||
title="Online Status">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl font-bold text-slate-900 dark:text-white tracking-tight">{{ user.name }}</h1>
|
||||
<p class="text-sm font-semibold text-slate-500 dark:text-slate-400 mb-6 uppercase tracking-wide">{{ user.role }}
|
||||
<h1 class="text-2xl font-bold text-slate-900 dark:text-white tracking-tight">{{
|
||||
userProfile.name }}
|
||||
</h1>
|
||||
<p
|
||||
class="text-sm font-semibold text-slate-500 dark:text-slate-400 mb-6 uppercase tracking-wide">
|
||||
{{ userProfile.role }}
|
||||
</p>
|
||||
|
||||
<div class="w-full space-y-3">
|
||||
<button
|
||||
@click=" activeTab = 'settings' "
|
||||
class="w-full py-2.5 px-4 bg-slate-900 dark:bg-blue-600 hover:bg-slate-800 dark:hover:bg-blue-500 text-white font-medium rounded-lg text-sm transition-all shadow-sm active:scale-95 cursor-pointer">
|
||||
Edit Profile
|
||||
</button>
|
||||
<button
|
||||
class="w-full py-2.5 px-4 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 font-medium rounded-lg text-sm transition-all active:scale-95 cursor-pointer">
|
||||
View Settings
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-slate-100 dark:border-slate-800 space-y-4">
|
||||
<div class="flex items-center gap-3 text-sm text-slate-600 dark:text-slate-400">
|
||||
<svg class="w-4 h-4 text-slate-400 dark:text-slate-500 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<svg class="w-4 h-4 text-slate-400 dark:text-slate-500 shrink-0" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span class="truncate">{{ user.email }}</span>
|
||||
<span class="truncate">{{ userProfile.email }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-sm text-slate-600 dark:text-slate-400">
|
||||
<svg class="w-4 h-4 text-slate-400 dark:text-slate-500 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<svg class="w-4 h-4 text-slate-400 dark:text-slate-500 shrink-0" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
<span>Polytechnic of Leiria</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-sm text-slate-600 dark:text-slate-400">
|
||||
<svg class="w-4 h-4 text-slate-400 dark:text-slate-500 shrink-0" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<svg class="w-4 h-4 text-slate-400 dark:text-slate-500 shrink-0" 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>
|
||||
@@ -68,13 +72,19 @@
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900 p-4 rounded-xl shadow-sm border border-slate-200 dark:border-slate-800 text-center hover:border-slate-300 dark:hover:border-slate-700 transition-colors">
|
||||
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ totalCitations }}</div>
|
||||
<div class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-1">Citations
|
||||
<div
|
||||
class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-1">
|
||||
Citations
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900 p-4 rounded-xl shadow-sm border border-slate-200 dark:border-slate-800 text-center hover:border-slate-300 dark:hover:border-slate-700 transition-colors">
|
||||
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ user.publications.length }}</div>
|
||||
<div class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-1">Papers
|
||||
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{
|
||||
userProfile.publications.length
|
||||
}}</div>
|
||||
<div
|
||||
class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-1">
|
||||
Papers
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,78 +92,176 @@
|
||||
|
||||
<div class="md:col-span-8 lg:col-span-9 space-y-6">
|
||||
|
||||
<div class="bg-white dark:bg-slate-900 p-1.5 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm flex gap-1 overflow-x-auto transition-colors">
|
||||
<button
|
||||
class="flex-1 min-w-[100px] py-2.5 px-4 rounded-lg text-lg font-semibold bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-white shadow-sm ring-1 ring-black/5 dark:ring-white/5 cursor-pointer transition-colors">
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900 p-1.5 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm flex gap-1 overflow-x-auto transition-colors">
|
||||
<button @click="activeTab = 'overview'"
|
||||
:class="activeTab === 'overview' ? 'bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-white shadow-sm ring-1 ring-black/5 dark:ring-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-50 dark:hover:bg-slate-800'"
|
||||
class="flex-1 min-w-[100px] py-2.5 px-4 rounded-lg text-lg font-semibold cursor-pointer transition-colors">
|
||||
Overview
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 min-w-[100px] py-2.5 px-4 rounded-lg text-lg font-medium text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors cursor-pointer">
|
||||
<button @click="activeTab = 'activity'"
|
||||
:class="activeTab === 'activity' ? 'bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-white shadow-sm ring-1 ring-black/5 dark:ring-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-50 dark:hover:bg-slate-800'"
|
||||
class="flex-1 min-w-[100px] py-2.5 px-4 rounded-lg text-lg font-medium transition-colors cursor-pointer">
|
||||
Activity Log
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 min-w-[100px] py-2.5 px-4 rounded-lg text-lg font-medium text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors cursor-pointer">
|
||||
<button @click="activeTab = 'settings'"
|
||||
:class="activeTab === 'settings' ? 'bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-white shadow-sm ring-1 ring-black/5 dark:ring-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-50 dark:hover:bg-slate-800'"
|
||||
class="flex-1 min-w-[100px] py-2.5 px-4 rounded-lg text-lg font-medium transition-colors cursor-pointer">
|
||||
Settings
|
||||
</button>
|
||||
</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">
|
||||
<h2 class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3">Biography</h2>
|
||||
<p class="text-slate-700 dark:text-slate-300 leading-relaxed text-sm">
|
||||
{{ user.biography }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between pt-2 px-1">
|
||||
<h2 class="text-lg font-bold text-slate-900 dark:text-white flex items-center gap-2">
|
||||
Recent Publications
|
||||
<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">{{
|
||||
user.publications.length }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div v-if="user.publications.length === 0"
|
||||
class="text-center py-16 border-2 border-dashed border-slate-200 dark:border-slate-800 rounded-xl bg-slate-50/50 dark:bg-slate-900/50">
|
||||
<p class="text-slate-500 dark:text-slate-400 font-medium">No publications found yet.</p>
|
||||
</div>
|
||||
|
||||
<div v-for="pub in user.publications" :key="pub.id"
|
||||
class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-200 dark:border-slate-800 hover:shadow-md hover:border-blue-300 dark:hover:border-blue-700 transition-all duration-200 group relative">
|
||||
<div class="flex justify-between items-start gap-4 mb-2">
|
||||
<h3
|
||||
class="text-base font-bold text-slate-900 dark:text-slate-100 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors leading-snug">
|
||||
<a href="#" class="before:absolute before:inset-0">{{ pub.title }}</a>
|
||||
</h3>
|
||||
<span
|
||||
class="shrink-0 px-2.5 py-1 rounded-md bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 text-xs font-bold border border-slate-200 dark:border-slate-700">
|
||||
{{ pub.publishedYear }}
|
||||
</span>
|
||||
<Transition mode="out-in" enter-active-class="transition duration-300 ease-out"
|
||||
enter-from-class="transform opacity-0 translate-y-2"
|
||||
enter-to-class="transform opacity-100 translate-y-0"
|
||||
leave-active-class="transition duration-200 ease-in"
|
||||
leave-from-class="transform opacity-100 translate-y-0"
|
||||
leave-to-class="transform opacity-0 translate-y-2">
|
||||
<div v-if="activeTab === 'overview'" key="overview" class="space-y-6">
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900 p-6 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-800 transition-colors">
|
||||
<h2
|
||||
class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3">
|
||||
Biography</h2>
|
||||
<p class="text-slate-700 dark:text-slate-300 leading-relaxed text-sm">
|
||||
{{ userProfile.biography }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="text-slate-600 dark:text-slate-400 text-sm leading-relaxed line-clamp-2">
|
||||
{{ pub.abstract }}
|
||||
</p>
|
||||
<div class="flex items-center justify-between pt-2 px-1">
|
||||
<h2 class="text-lg font-bold text-slate-900 dark:text-white flex items-center gap-2">
|
||||
Recent Publications
|
||||
<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">
|
||||
{{ userProfile.publications.length }}
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div v-if="userProfile.publications.length === 0"
|
||||
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="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">
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 flex flex-col items-center">
|
||||
<div
|
||||
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">
|
||||
<svg class="w-8 h-8 text-blue-500 dark:text-blue-400" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
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>
|
||||
|
||||
<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
|
||||
@click="goToCreatePost()"
|
||||
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">
|
||||
<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 v-for="pub in userProfile.publications" :key="pub.id"
|
||||
class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-200 dark:border-slate-800 hover:shadow-md hover:border-blue-300 dark:hover:border-blue-700 transition-all duration-200 group relative">
|
||||
<div class="flex justify-between items-start gap-4 mb-2">
|
||||
<h3 class="text-base font-bold text-slate-900 dark:text-slate-100">{{ pub.title
|
||||
}}</h3>
|
||||
<span
|
||||
class="shrink-0 px-2.5 py-1 rounded-md bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 text-xs font-bold border border-slate-200 dark:border-slate-700">
|
||||
{{ pub.publishedYear }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-slate-600 dark:text-slate-400 text-sm leading-relaxed line-clamp-2">
|
||||
{{ pub.abstract }}</p>
|
||||
<div
|
||||
class="mt-4 pt-4 border-t border-slate-50 dark:border-slate-800 flex flex-wrap items-center gap-4 text-xs font-medium">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 border border-blue-100 dark:border-blue-800/50">
|
||||
{{ pub.field }}
|
||||
</span>
|
||||
<span>{{ pub.citations }} Citations</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="activeTab === 'activity'" key="activity" class="space-y-4">
|
||||
<h2 class="text-lg font-bold text-slate-900 dark:text-white px-1">User History</h2>
|
||||
|
||||
<div v-if="activityLog.length === 0"
|
||||
class="text-center py-12 border border-dashed border-slate-200 dark:border-slate-800 rounded-xl">
|
||||
<p class="text-slate-500">No activity recorded.</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-4 pt-4 border-t border-slate-50 dark:border-slate-800 flex flex-wrap items-center gap-4 text-xs font-medium z-10 relative">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 border border-blue-100 dark:border-blue-800/50">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-blue-500"></span>
|
||||
{{ pub.field }}
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1.5 text-slate-500 dark:text-slate-400">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</svg>
|
||||
{{ pub.citations }} Citations
|
||||
</span>
|
||||
<span class="text-slate-300 dark:text-slate-700">•</span>
|
||||
<span class="text-slate-500 dark:text-slate-400">{{ pub.publishedDate }}</span>
|
||||
class="bg-white dark:bg-slate-900 rounded-xl shadow-sm border border-slate-200 dark:border-slate-800 overflow-hidden">
|
||||
<div v-for="(item, index) in activityLog" :key="item.id"
|
||||
class="p-4 flex gap-4 border-b border-slate-100 dark:border-slate-800 last:border-0 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
|
||||
|
||||
<div class="flex flex-col items-center min-w-[80px] text-center pt-1">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase">
|
||||
{{ new Date(item.timestamp).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
}) }}
|
||||
</span>
|
||||
<span class="text-[10px] text-slate-400">
|
||||
{{ new Date(item.timestamp).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span
|
||||
class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wide"
|
||||
:class="{
|
||||
'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400': item.action.includes('UPLOAD'),
|
||||
'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400': item.action.includes('COMMENT'),
|
||||
'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400': item.action.includes('RATED') || item.action.includes('CORRECT'),
|
||||
'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400': item.action.includes('TAG')
|
||||
}">
|
||||
{{ item.action.replace('_', ' ') }}
|
||||
</span>
|
||||
<span class="text-xs text-slate-400 font-medium">{{ item.target_type }} #{{
|
||||
item.target_id }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-700 dark:text-slate-300">
|
||||
{{ item.details }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="activeTab === 'settings'" key="settings"
|
||||
class="bg-white dark:bg-slate-900 p-6 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-800 transition-colors">
|
||||
<h2
|
||||
class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3">
|
||||
Settings</h2>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400">
|
||||
Nothing here but chickens! Settings coming soon.
|
||||
</p>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,16 +270,41 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAuthStore } from "~/stores/auth-store.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
enum Role {
|
||||
STUDENT = "STUDENT",
|
||||
TEACHER = "TEACHER",
|
||||
ADMIN = "ADMIN",
|
||||
RESEARCHER = "RESEARCHER"
|
||||
// --- Config & Store ---
|
||||
const config = useRuntimeConfig();
|
||||
const api = config.public.apiBase;
|
||||
const authStore = useAuthStore();
|
||||
const { token } = storeToRefs(authStore);
|
||||
const router = useRouter();
|
||||
|
||||
// --- Interfaces ---
|
||||
|
||||
interface Tag {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Publication {
|
||||
// Matches response from EP12 (/api/v1/publications/my-submissions) [cite: 296]
|
||||
interface ApiPublication {
|
||||
id: number;
|
||||
title: string;
|
||||
authors: string[];
|
||||
publication_date: string;
|
||||
type: string;
|
||||
rating: number; // Mapping this to citations for the UI
|
||||
comment_count: number;
|
||||
attached_file?: string;
|
||||
tags: Tag[];
|
||||
abstract?: string;
|
||||
}
|
||||
|
||||
// Matches your UI requirements
|
||||
interface UIPublication {
|
||||
id: number;
|
||||
title: string;
|
||||
abstract: string;
|
||||
@@ -181,63 +314,131 @@ interface Publication {
|
||||
field: string;
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: Role;
|
||||
isActive: boolean;
|
||||
avatarUrl: string;
|
||||
joinDate: string;
|
||||
publications: Publication[];
|
||||
}
|
||||
|
||||
const user = ref<User>({
|
||||
id: 1,
|
||||
name: 'Dr. Maria Santos',
|
||||
email: '[email protected]',
|
||||
biography: 'Focused on distributed systems and IoT security. Currently leading the research group on "Smart City Infrastructures" at IPLeiria. My work primarily involves developing scalable architectures for real-time data processing and ensuring data integrity in edge computing environments.',
|
||||
role: Role.RESEARCHER,
|
||||
isActive: true,
|
||||
avatarUrl: 'https://api.dicebear.com/7.x/notionists/svg?seed=Maria',
|
||||
joinDate: '2023-09-15',
|
||||
publications: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Machine Learning Approaches to Predictive Maintenance in Industrial IoT',
|
||||
abstract: 'This paper explores novel machine learning techniques for predictive maintenance in industrial Internet of Things environments, demonstrating a 35% improvement in fault detection accuracy.',
|
||||
publishedDate: 'Nov 20, 2024',
|
||||
publishedYear: '2024',
|
||||
citations: 12,
|
||||
field: 'Computer Science'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Distributed Systems Architecture for Real-Time Data Processing',
|
||||
abstract: 'We present a scalable distributed architecture designed for real-time processing of large-scale sensor data streams. The proposed system leverages Kafka for ingestion.',
|
||||
publishedDate: 'Aug 05, 2024',
|
||||
publishedYear: '2024',
|
||||
citations: 8,
|
||||
field: 'Software Engineering'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Security Considerations in Edge Computing Environments',
|
||||
abstract: 'An in-depth analysis of security vulnerabilities in edge computing deployments. This paper proposes a layered security framework that addresses both hardware and software.',
|
||||
publishedDate: 'Mar 12, 2024',
|
||||
publishedYear: '2024',
|
||||
citations: 23,
|
||||
field: 'Cybersecurity'
|
||||
}
|
||||
]
|
||||
// --- State ---
|
||||
const loading = ref(true);
|
||||
const activeTab = ref('overview'); // State to control visible tab
|
||||
const activityLog = ref<ActivityItem[]>([]); // Store history data
|
||||
|
||||
const userProfile = ref({
|
||||
id: 0,
|
||||
name: 'Loading...',
|
||||
email: '',
|
||||
role: '',
|
||||
is_active: false,
|
||||
avatarUrl: '',
|
||||
biography: 'No biography provided.',
|
||||
joinDate: new Date().toISOString(),
|
||||
publications: [] as any[]
|
||||
});
|
||||
|
||||
const totalCitations = computed(() => user.value.publications.reduce((sum, pub) => sum + pub.citations, 0));
|
||||
// --- API Actions ---
|
||||
|
||||
// Fetch Tags (EP23) - To populate the select box
|
||||
async function fetchTags() {
|
||||
if (!token.value) return;
|
||||
try {
|
||||
const data = await $fetch<Tag[]>(`${api}/tags`, {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${token.value}` }
|
||||
});
|
||||
availableTags.value = data || [];
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch tags", e);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Computed ---
|
||||
const totalCitations = computed(() =>
|
||||
userProfile.value.publications.reduce((sum, pub) => sum + (pub.citations || 0), 0)
|
||||
);
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
return new Date(user.value.joinDate).toLocaleDateString('en-US', {
|
||||
return new Date(userProfile.value.joinDate).toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
});
|
||||
|
||||
// --- API Actions ---
|
||||
|
||||
// 1. Fetch Activity Log (EP06)
|
||||
async function fetchActivityHistory() {
|
||||
if (!token.value) return;
|
||||
|
||||
try {
|
||||
// GET /api/v1/me/history
|
||||
const data = await $fetch<ActivityItem[]>(`${api}/me/history`, {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${token.value}` }
|
||||
});
|
||||
activityLog.value = data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching history:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fetch Main Profile Data (EP03 & EP12)
|
||||
async function fetchProfileData() {
|
||||
if (!token.value) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
// Fetch User Details
|
||||
const userData = await $fetch(`${api}/users/me`, {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${token.value}` }
|
||||
});
|
||||
|
||||
// Fetch Publications
|
||||
// const publicationsData = await $fetch(`${api}/publications/my-submissions`, {
|
||||
// method: 'GET',
|
||||
// headers: { Authorization: `Bearer ${token.value}` }
|
||||
// });
|
||||
|
||||
// Map Data
|
||||
userProfile.value = {
|
||||
...userProfile.value,
|
||||
id: userData.id,
|
||||
name: userData.name,
|
||||
email: userData.email,
|
||||
role: userData.role,
|
||||
is_active: userData.is_active,
|
||||
avatarUrl: `https://ui-avatars.com/api/?name=${encodeURIComponent(userData.name)}&background=0D8ABC&color=fff`
|
||||
};
|
||||
|
||||
userProfile.value.publications = (publicationsData as any[]).map(pub => {
|
||||
const pubDate = new Date(pub.publication_date);
|
||||
return {
|
||||
id: pub.id,
|
||||
title: pub.title,
|
||||
abstract: pub.abstract || 'No abstract available.',
|
||||
publishedDate: pubDate.toLocaleDateString(),
|
||||
publishedYear: pubDate.getFullYear().toString(),
|
||||
citations: pub.rating || 0,
|
||||
field: pub.tags && pub.tags.length > 0 ? pub.tags[0].name : 'General'
|
||||
};
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching profile:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Redirects ---
|
||||
const goToCreatePost = () => {
|
||||
console.log("Navigating to create post modal...");
|
||||
router.push({
|
||||
path: '/',
|
||||
query: { openCreatePostModal: 'true' }
|
||||
});
|
||||
};
|
||||
|
||||
// --- Lifecycle ---
|
||||
onMounted(() => {
|
||||
fetchProfileData();
|
||||
fetchActivityHistory(); // Fetch history on mount
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user