Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
951bed6fbf |
@@ -0,0 +1,171 @@
|
|||||||
|
|
||||||
|
:root {
|
||||||
|
--text-primary: #000000;
|
||||||
|
--text-secondary: #8d96a0;
|
||||||
|
--text-tertiary: #6e7681;
|
||||||
|
--text-link: #2f81f7;
|
||||||
|
--text-link-hover: #539bf5;
|
||||||
|
|
||||||
|
--border-primary: #30363d;
|
||||||
|
--border-hover: #8d96a0;
|
||||||
|
|
||||||
|
--accent: #7aa2f7;
|
||||||
|
--accent-hover: #9ab6ff;
|
||||||
|
--accent-primary: #2f81f7;
|
||||||
|
--accent-emphasis: #539bf5;
|
||||||
|
|
||||||
|
--success: #3fb950;
|
||||||
|
--warning: #d29922;
|
||||||
|
--danger: #f85149;
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||||
|
"Noto Sans", Helvetica, Arial, sans-serif;
|
||||||
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
|
||||||
|
/* Radius */
|
||||||
|
--radius-sm: 0.375rem;
|
||||||
|
--radius-md: 0.5rem;
|
||||||
|
--radius-lg: 0.75rem;
|
||||||
|
--radius-full: 9999px;
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
color: var(--text-primary);
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--text-link);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--text-link-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEMANTIC COMPONENTS (SAFE)
|
||||||
|
============================================== */
|
||||||
|
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--bg-surface);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.app-card {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card:hover {
|
||||||
|
border-color: var(--border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.app-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-btn-primary {
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-btn-primary:hover {
|
||||||
|
background-color: var(--accent-emphasis);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-btn-secondary {
|
||||||
|
background-color: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-btn-secondary:hover {
|
||||||
|
background-color: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
transition: background var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.app-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-badge-success {
|
||||||
|
color: var(--success);
|
||||||
|
background: color-mix(in srgb, var(--success) 15%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.app-avatar {
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
+240
-2
@@ -1,5 +1,243 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<title> Profile - {{user.name}}</title>
|
||||||
<h1>This will be the profile page! HORRAY!</h1>
|
<div class="min-h-screen bg-slate-50 font-sans text-slate-900 selection:bg-blue-100 pb-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="md:col-span-4 lg:col-span-3 flex flex-col gap-6">
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl p-6 shadow-sm border border-slate-200">
|
||||||
|
<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 bg-slate-100 overflow-hidden shadow-md ring-1 ring-slate-200">
|
||||||
|
<img :src="user.avatarUrl" :alt="user.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 shadow-sm"
|
||||||
|
:class="user.isActive ? 'bg-emerald-500' : 'bg-gray-400'" title="Online Status">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold text-slate-900 tracking-tight">{{ user.name }}</h1>
|
||||||
|
<p class="text-sm font-semibold text-slate-500 mb-6 uppercase tracking-wide">{{ user.role }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="w-full space-y-3">
|
||||||
|
<button
|
||||||
|
class="w-full py-2.5 px-4 bg-slate-900 hover:bg-slate-800 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 border border-slate-300 hover:bg-slate-50 text-slate-700 font-medium rounded-lg text-sm transition-all active:scale-95 cursor-pointer">
|
||||||
|
View Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8 pt-6 border-t border-slate-100 space-y-4">
|
||||||
|
<div class="flex items-center gap-3 text-sm text-slate-600">
|
||||||
|
<svg class="w-4 h-4 text-slate-400 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>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3 text-sm text-slate-600">
|
||||||
|
<svg class="w-4 h-4 text-slate-400 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">
|
||||||
|
<svg class="w-4 h-4 text-slate-400 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>
|
||||||
|
<span>Joined {{ formattedDate }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div
|
||||||
|
class="bg-white p-4 rounded-xl shadow-sm border border-slate-200 text-center hover:border-slate-300 transition-colors">
|
||||||
|
<div class="text-2xl font-bold text-slate-900">{{ totalCitations }}</div>
|
||||||
|
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">Citations
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-white p-4 rounded-xl shadow-sm border border-slate-200 text-center hover:border-slate-300 transition-colors">
|
||||||
|
<div class="text-2xl font-bold text-slate-900">{{ user.publications.length }}</div>
|
||||||
|
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">Papers
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="md:col-span-8 lg:col-span-9 space-y-6">
|
||||||
|
|
||||||
|
<div class="bg-white p-1.5 rounded-xl border border-slate-200 shadow-sm flex gap-1 overflow-x-auto">
|
||||||
|
<button
|
||||||
|
class="flex-1 min-w-[100px] py-2.5 px-4 rounded-lg text-lg font-semibold bg-slate-100 text-slate-900 shadow-sm ring-1 ring-black/5 cursor-pointer">
|
||||||
|
Overview
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="flex-1 min-w-[100px] py-2.5 px-4 rounded-lg text-lg font-medium text-slate-500 hover:text-slate-900 hover:bg-slate-50 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 hover:text-slate-900 hover:bg-slate-50 transition-colors cursor-pointer">
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
|
||||||
|
<h2 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3">Biography</h2>
|
||||||
|
<p class="text-slate-700 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 flex items-center gap-2">
|
||||||
|
Recent Publications
|
||||||
|
<span class="bg-slate-200 text-slate-700 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 rounded-xl bg-slate-50/50">
|
||||||
|
<p class="text-slate-500 font-medium">No publications found yet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-for="pub in user.publications" :key="pub.id"
|
||||||
|
class="bg-white p-6 rounded-xl shadow-sm border border-slate-200 hover:shadow-md hover:border-blue-300 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 group-hover:text-blue-600 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 text-slate-600 text-xs font-bold border border-slate-200">
|
||||||
|
{{ pub.publishedYear }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-slate-600 text-sm leading-relaxed line-clamp-2">
|
||||||
|
{{ pub.abstract }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mt-4 pt-4 border-t border-slate-50 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 text-blue-700 border border-blue-100">
|
||||||
|
<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">
|
||||||
|
<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">•</span>
|
||||||
|
<span class="text-slate-500">{{ pub.publishedDate }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
STUDENT = "STUDENT",
|
||||||
|
TEACHER = "TEACHER",
|
||||||
|
ADMIN = "ADMIN",
|
||||||
|
RESEARCHER = "RESEARCHER"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Publication {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
abstract: string;
|
||||||
|
publishedDate: string;
|
||||||
|
publishedYear: string;
|
||||||
|
citations: number;
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalCitations = computed(() => user.value.publications.reduce((sum, pub) => sum + pub.citations, 0));
|
||||||
|
|
||||||
|
const formattedDate = computed(() => {
|
||||||
|
return new Date(user.value.joinDate).toLocaleDateString('en-US', {
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
+27
-24
@@ -2,31 +2,34 @@ import tailwindcss from "@tailwindcss/vite";
|
|||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: "2025-07-15",
|
compatibilityDate: "2025-07-15",
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
|
|
||||||
css: ["~/assets/css/tailwind.css"],
|
css: [
|
||||||
vite: {
|
"~/assets/css/main.css",
|
||||||
plugins: [tailwindcss()],
|
"~/assets/css/tailwind.css"
|
||||||
},
|
],
|
||||||
|
vite: {
|
||||||
|
plugins: [tailwindcss()],
|
||||||
|
},
|
||||||
|
|
||||||
modules: [
|
modules: [
|
||||||
"@nuxt/fonts",
|
"@nuxt/fonts",
|
||||||
"@nuxt/icon",
|
"@nuxt/icon",
|
||||||
"@nuxt/image",
|
"@nuxt/image",
|
||||||
"@nuxt/eslint",
|
"@nuxt/eslint",
|
||||||
"shadcn-nuxt",
|
"shadcn-nuxt",
|
||||||
],
|
],
|
||||||
|
|
||||||
shadcn: {
|
shadcn: {
|
||||||
/**
|
/**
|
||||||
* Prefix for all the imported component
|
* Prefix for all the imported component
|
||||||
*/
|
*/
|
||||||
prefix: "",
|
prefix: "",
|
||||||
/**
|
/**
|
||||||
* Directory that the component lives in.
|
* Directory that the component lives in.
|
||||||
* @default "./app/components/ui"
|
* @default "./app/components/ui"
|
||||||
*/
|
*/
|
||||||
componentDir: "./app/components/ui",
|
componentDir: "./app/components/ui",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user