Author SHA1 Message Date
Edd 901244ab48 Merge pull request 'fix frontend bugs' (#19) from na/fix-frontend into master
Reviewed-on: https://gitea.fernandovideira.com/SyntaxSquad/DAEProject_Frontend/pulls/19
Reviewed-by: FernandoJVideira <[email protected]>
Reviewed-by: Afonso Clerigo Mendes de Sousa <[email protected]>
2026-01-26 22:01:32 +00:00
Edd 7902992b77 Merge branch 'master' into na/fix-frontend 2026-01-26 19:14:57 +00:00
Edd 29ebda9b18 fix frontend bugs 2026-01-26 19:12:50 +00:00
FernandoJVideira b5dff50d18 Merge pull request 'LET'S FUCKING GOOOOO' (#18) from develop into master
Reviewed-on: https://gitea.fernandovideira.com/SyntaxSquad/DAEProject_Frontend/pulls/18
Reviewed-by: Afonso Clerigo Mendes de Sousa <[email protected]>
Reviewed-by: Edd <[email protected]>
2026-01-23 21:47:54 +00:00
FernandoJVideira b60432f1b9 Merge pull request 'THIS-IS-THE-LAST-COMMIT-I-PROMISE-XoXo-PIXA-GORDA' (#17) from THIS-IS-THE-LAST-COMMIT-I-PROMISE-XoXo-PIXA-GORDA into develop
Reviewed-on: https://gitea.fernandovideira.com/SyntaxSquad/DAEProject_Frontend/pulls/17
Reviewed-by: FernandoJVideira <[email protected]>
2026-01-23 21:46:37 +00:00
AfonsoCMSousa af6ca88fd4 dont read the name of the branch 2026-01-23 21:45:49 +00:00
AfonsoCMSousa e7b9fdb1bd Merge pull request 'Final front end' (#16) from na/final-frontend into develop
Reviewed-on: https://gitea.fernandovideira.com/SyntaxSquad/DAEProject_Frontend/pulls/16
Reviewed-by: FernandoJVideira <[email protected]>
Reviewed-by: Afonso Clerigo Mendes de Sousa <[email protected]>
2026-01-23 21:23:09 +00:00
4 changed files with 122 additions and 10 deletions
+108
View File
@@ -11,6 +11,15 @@
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"> 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"> <div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-4">
<button
class="flex items-center gap-2 text-xs font-bold text-gray-500 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
@click="showHistory = true">
<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 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
View history
</button>
<span <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"> 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">
{{ post.type.replace('_', ' ') }} {{ post.type.replace('_', ' ') }}
@@ -256,6 +265,53 @@
</div> </div>
</article> </article>
</div> </div>
<Transition enter-active-class="transition duration-200 ease-out" enter-from-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100" leave-active-class="transition duration-150 ease-in"
leave-from-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95">
<div v-if="showHistory"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div
class="bg-white dark:bg-gray-900 w-full max-w-2xl rounded-2xl shadow-xl border dark:border-gray-800 overflow-hidden">
<!-- Header -->
<div class="flex justify-between items-center px-6 py-4 border-b dark:border-gray-800">
<h3 class="text-lg font-bold text-gray-900 dark:text-white">
Publication history
</h3>
<button class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
@click="showHistory = false">
</button>
</div>
<!-- Body -->
<div class="p-6 space-y-4 max-h-[60vh] overflow-y-auto">
<div v-for="entry in publicationHistory" :key="entry.id"
class="p-4 rounded-xl border border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-800/50">
<div class="flex items-start gap-3">
<!-- Action tag -->
<span class="px-2 py-0.5 rounded-md text-[10px] font-bold uppercase tracking-wide"
:class="actionTagClass(entry.actionType)">
{{ formatAction(entry.actionType) }}
</span>
</div>
<div class="mt-2 flex items-start gap-4">
<!-- Text -->
<div class="flex-1">
<div class="text-sm text-gray-700 dark:text-gray-300">
{{ entry.description }}
</div>
<div class="mt-1 text-xs text-gray-400">
{{ new Date(entry.timestamp).toLocaleString() }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Transition>
</template> </template>
<script setup> <script setup>
@@ -277,6 +333,43 @@ const showCommentInput = ref(false);
const totalComments = ref(0); const totalComments = ref(0);
const editingCommentId = ref(null); const editingCommentId = ref(null);
const editText = ref(''); const editText = ref('');
const showHistory = ref(false)
const publicationHistory = ref([])
const actionTagClass = (type) => {
const map = {
// Publication lifecycle
CREATE_PUBLICATION: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300',
DELETE_PUBLICATION: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
EDIT_PUBLICATION: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
TAG_PUBLICATION: 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300',
HIDE_PUBLICATION: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300',
// Interaction
COMMENT_PUBLICATION: 'bg-sky-100 text-sky-700 dark:bg-sky-900/40 dark:text-sky-300',
EDIT_COMMENT: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300',
HIDE_COMMENT: 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300',
RATE_PUBLICATION: 'bg-pink-100 text-pink-700 dark:bg-pink-900/40 dark:text-pink-300',
// User / admin actions
CREATE_USER: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300',
DELETE_USER: 'bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300',
CHANGE_ROLE: 'bg-teal-100 text-teal-700 dark:bg-teal-900/40 dark:text-teal-300',
CHANGE_EMAIL: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/40 dark:text-cyan-300',
CHANGE_NAME: 'bg-lime-100 text-lime-700 dark:bg-lime-900/40 dark:text-lime-300',
// Fallback
UPLOAD_PUBLICATION: 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
}
return map[type] ?? 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
}
const formatAction = (type) => {
return type
.replace(/_/g, ' ')
.toLowerCase()
}
const toggleHide = async (comment) => { const toggleHide = async (comment) => {
try { try {
@@ -343,6 +436,18 @@ const fetchComments = async () => {
} }
}; };
const fetchPublicationHistory = async () => {
try {
const history = await $fetch(`${props.api}/publications/${props.post.id}/history`, {
headers: { Authorization: `Bearer ${props.token}` }
});
return history;
} catch (err) {
console.error("Publication history fetch error", err);
return [];
}
};
const handleCommentSubmit = async () => { const handleCommentSubmit = async () => {
submitting.value = true; submitting.value = true;
try { try {
@@ -391,5 +496,8 @@ const downloadFile = async () => {
onMounted(() => { onMounted(() => {
fetchComments(); fetchComments();
fetchPublicationHistory().then(history => {
publicationHistory.value = history;
});
}); });
</script> </script>
-5
View File
@@ -165,13 +165,11 @@ import { useAuthStore } from "~/stores/auth-store.js";
const authStore = useAuthStore(); const authStore = useAuthStore();
const router = useRouter(); const router = useRouter();
// State
const isDark = ref(false); const isDark = ref(false);
const isUserMenuOpen = ref(false); const isUserMenuOpen = ref(false);
const isMobileMenuOpen = ref(false); const isMobileMenuOpen = ref(false);
const userMenuRef = ref(null); const userMenuRef = ref(null);
// Toggle Dark Mode
function toggleDark() { function toggleDark() {
isDark.value = !isDark.value; isDark.value = !isDark.value;
if (isDark.value) { if (isDark.value) {
@@ -183,7 +181,6 @@ function toggleDark() {
} }
} }
// Navigation Links
const navLinks = [ const navLinks = [
{ name: 'Profile', path: '/profile' }, { name: 'Profile', path: '/profile' },
]; ];
@@ -208,13 +205,11 @@ function closeDropdowns(event) {
} }
onMounted(() => { onMounted(() => {
// Initialize Theme
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
isDark.value = true; isDark.value = true;
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
} }
// Click listener for closing dropdowns
window.addEventListener('click', closeDropdowns); window.addEventListener('click', closeDropdowns);
}); });
+3
View File
@@ -53,6 +53,7 @@
import { useAuthStore } from "~/stores/auth-store.js"; import { useAuthStore } from "~/stores/auth-store.js";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
const notifications = useNotificationStore();
const config = useRuntimeConfig(); const config = useRuntimeConfig();
const api = config.public.apiBase; const api = config.public.apiBase;
const authStore = useAuthStore(); const authStore = useAuthStore();
@@ -94,8 +95,10 @@ async function handleLogin() {
token.value = response._data.token; token.value = response._data.token;
user.value = response._data.user; user.value = response._data.user;
if (response._data.user.mustChangePassword === true) { if (response._data.user.mustChangePassword === true) {
notifications.error("Please update your password.");
router.push("/profile/update-password"); router.push("/profile/update-password");
} else { } else {
notifications.success("Logged in successfully!");
router.push("/"); router.push("/");
} }
} }
+10 -4
View File
@@ -64,8 +64,8 @@
<select v-model="sortBy" <select v-model="sortBy"
class="bg-white dark:bg-gray-900 border dark:border-gray-800 rounded-l-lg px-3 py-2 text-xs font-bold uppercase outline-none focus:ring-1 focus:ring-blue-500 cursor-pointer border-r-0"> class="bg-white dark:bg-gray-900 border dark:border-gray-800 rounded-l-lg px-3 py-2 text-xs font-bold uppercase outline-none focus:ring-1 focus:ring-blue-500 cursor-pointer border-r-0">
<option value="date">Date</option> <option value="date">Date</option>
<option value="votes">Rating</option> <option value="rating">Rating</option>
<option value="comments">Comments</option> <option value="comment">Comments</option>
</select> </select>
<button <button
class="bg-white dark:bg-gray-900 border dark:border-gray-800 rounded-r-lg px-3 py-2 text-xs font-bold hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" class="bg-white dark:bg-gray-900 border dark:border-gray-800 rounded-r-lg px-3 py-2 text-xs font-bold hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
@@ -387,6 +387,7 @@ const fetchTags = async () => {
subscribedTags.value = myTags.map(t => t.id); subscribedTags.value = myTags.map(t => t.id);
} catch (err) { } catch (err) {
console.error("Error fetching tags:", err); console.error("Error fetching tags:", err);
notifications.error("Failed to subscribe to tag");
} }
}; };
@@ -413,6 +414,7 @@ const toggleTagSubscription = async (tag) => {
else subscribedTags.value.push(tag.id); else subscribedTags.value.push(tag.id);
console.error("Sync failed:", err); console.error("Sync failed:", err);
notifications.error("Failed to subscribe to tag");
} }
}; };
@@ -451,6 +453,7 @@ const loadPosts = async () => {
} }
} catch (err) { } catch (err) {
console.error("Failed to fetch posts:", err); console.error("Failed to fetch posts:", err);
notifications.error("Failed to load publications");
} finally { } finally {
loading.value = false; loading.value = false;
} }
@@ -465,6 +468,7 @@ const fetchPostDetail = async (id) => {
selectedPost.value = postDetail; selectedPost.value = postDetail;
} catch (err) { } catch (err) {
console.error("Failed to fetch post detail:", err); console.error("Failed to fetch post detail:", err);
notifications.error("Failed to fetch details");
} finally { } finally {
loading.value = false; loading.value = false;
} }
@@ -497,6 +501,8 @@ const votePost = async (targetPost, direction) => {
selectedPost.value.my_vote = response.my_vote; selectedPost.value.my_vote = response.my_vote;
} }
notifications.success("Voted with success");
} catch (error) { } catch (error) {
console.error("Vote failed", error); console.error("Vote failed", error);
notifications.error("Failed to submit vote."); notifications.error("Failed to submit vote.");
@@ -514,9 +520,9 @@ const filteredPosts = computed(() => {
return result.sort((a, b) => { return result.sort((a, b) => {
const modifier = sortDesc.value ? 1 : -1; const modifier = sortDesc.value ? 1 : -1;
if (sortBy.value === 'votes') { if (sortBy.value === 'rating') {
return (b.rating - a.rating) * modifier; return (b.rating - a.rating) * modifier;
} else if (sortBy.value === 'comments') { } else if (sortBy.value === 'comment') {
return (b.commentCount - a.commentCount) * modifier; return (b.commentCount - a.commentCount) * modifier;
} else { } else {
return (new Date(b.publicationDate).getTime() - new Date(a.publicationDate).getTime()) * modifier; return (new Date(b.publicationDate).getTime() - new Date(a.publicationDate).getTime()) * modifier;