feat: Implement enhanced user profiles with social features including direct messaging, post comments, and reposts, and introduce new routing for Docs and Changelog views.

This commit is contained in:
2026-01-18 13:10:12 +01:00
parent 959b453d69
commit 62280265b4
23 changed files with 1826 additions and 458 deletions

View File

@@ -2,17 +2,19 @@
import { ref, onUpdated, nextTick } from 'vue';
import { useChatStore } from '../stores/chat';
import { storeToRefs } from 'pinia';
import { Send, Hash, Smile } from 'lucide-vue-next';
import { Send, Hash, Smile, ExternalLink, Copy, Check } from 'lucide-vue-next';
const chatStore = useChatStore();
const { currentMessages, currentChannel, walletAddress } = storeToRefs(chatStore);
const newMessage = ref('');
const messagesContainer = ref(null);
const showEmojiPicker = ref(null); // messageId
const showEmojiPicker = ref(null);
const copiedTxId = ref(null);
const EMOJIS = ['👍', '❤️', '🔥', '😂', '😮', '😢'];
const EMOJIS = ['👍', '❤️', '🔥', '😂', '😮', '😢', '🚀', '💎'];
const toggleReaction = (messageId, emoji) => {
console.log('Toggling reaction:', messageId, emoji);
chatStore.toggleReaction(messageId, emoji);
showEmojiPicker.value = null;
};
@@ -51,18 +53,21 @@ const formatTime = (isoString) => {
return new Date(isoString).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};
const copyTxId = (txId) => {
navigator.clipboard.writeText(txId);
copiedTxId.value = txId;
setTimeout(() => { copiedTxId.value = null; }, 2000);
};
const emit = defineEmits(['view-profile']);
</script>
<template>
<div class="flex-1 flex flex-col h-full bg-discord-dark relative z-10">
<!-- Header (Desktop only, mobile header is in ChatLayout) -->
<!-- Header (Desktop only) -->
<div class="hidden md:flex h-12 border-b border-black/20 items-center px-4 shadow-sm bg-discord-dark/95 backdrop-blur-sm">
<div class="text-sm font-bold text-white flex items-center gap-2">
<Hash
size="18"
class="text-gray-400"
/>
<Hash size="18" class="text-gray-400" />
{{ currentChannel }}
</div>
</div>
@@ -88,9 +93,12 @@ const emit = defineEmits(['view-profile']);
<div
v-for="(msg, index) in currentMessages"
:key="msg.id || msg.tempId"
class="group flex gap-4 px-4 py-1 hover:bg-white/[0.02] transition-colors relative"
:class="[
'group flex gap-4 px-4 py-2 transition-all relative rounded-lg',
msg.status === 'failed' ? 'bg-red-500/5 border border-red-500/20' : 'hover:bg-white/[0.02]'
]"
>
<!-- Avatar (only if first message in group) -->
<!-- Avatar -->
<div class="w-10 flex-shrink-0">
<div
v-if="index === 0 || currentMessages[index-1].walletAddress !== msg.walletAddress"
@@ -112,7 +120,7 @@ const emit = defineEmits(['view-profile']);
<div class="flex-1 min-w-0">
<div
v-if="index === 0 || currentMessages[index-1].walletAddress !== msg.walletAddress"
class="flex items-center gap-2 mb-0.5"
class="flex items-center gap-2 mb-1 flex-wrap"
>
<span
:class="['text-sm font-bold hover:underline cursor-pointer', msg.walletAddress === walletAddress ? 'text-violet-400' : 'text-white']"
@@ -122,35 +130,61 @@ const emit = defineEmits(['view-profile']);
</span>
<span class="text-[10px] text-crypto-muted">{{ formatTime(msg.timestamp) }}</span>
<!-- Status LED -->
<div
v-if="msg.status"
class="led ml-1"
:class="{
'led-orange': msg.status === 'pending',
'led-green': msg.status === 'validated',
'led-red': msg.status === 'failed'
}"
:title="msg.status"
/>
</div>
<div :class="['text-sm leading-relaxed break-words', msg.status === 'failed' ? 'text-status-failed line-through opacity-60' : 'text-gray-100']">
{{ msg.content }}
<!-- Message Content & Status -->
<div class="flex items-start gap-2">
<div :class="['text-sm leading-relaxed break-words flex-1', msg.status === 'failed' ? 'text-red-400 line-through' : 'text-gray-100']">
{{ msg.content }}
</div>
<!-- Transaction ID & Status Pill for all messages -->
<div class="flex items-center gap-2 flex-shrink-0 mt-1">
<span
v-if="msg.txId && msg.status !== 'failed'"
class="flex items-center gap-1.5 text-[10px] font-mono text-gray-500 bg-black/20 px-1.5 py-0.5 rounded opacity-0 group-hover:opacity-100 transition-opacity"
>
<ExternalLink size="10" />
{{ msg.txId.slice(0, 8) }}
<button
class="hover:text-fuchsia-400 transition-colors"
@click="copyTxId(msg.txId)"
>
<Check v-if="copiedTxId === msg.txId" size="10" class="text-green-400" />
<Copy v-else size="10" />
</button>
</span>
<div
v-if="msg.status"
class="led"
:class="{
'led-orange animate-pulse': msg.status === 'pending',
'led-green': msg.status === 'validated',
'led-red': msg.status === 'failed'
}"
/>
</div>
</div>
<!-- Failed message notice -->
<div v-if="msg.status === 'failed'" class="text-[10px] text-red-400 mt-1 flex items-center gap-1">
Transaction failed - message not saved
</div>
<!-- Reactions Display -->
<div
v-if="msg.reactions && msg.reactions.length > 0"
class="flex flex-wrap gap-1 mt-1.5"
class="flex flex-wrap gap-1 mt-2"
>
<button
v-for="emoji in getUniqueEmojis(msg.reactions)"
:key="emoji"
:class="['flex items-center gap-1.5 px-2 py-0.5 rounded-lg text-xs border transition-all animate-pop-in',
:class="['flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs border transition-all',
hasUserReacted(msg.reactions, emoji)
? 'bg-violet-500/20 border-violet-500/50 text-violet-300'
: 'bg-white/5 border-white/10 text-gray-400 hover:bg-white/10']"
? 'bg-violet-500/20 border-violet-500/50 text-violet-300 shadow-sm shadow-violet-500/20'
: 'bg-white/5 border-white/10 text-gray-400 hover:bg-white/10 hover:border-white/20']"
@click="toggleReaction(msg.id, emoji)"
>
<span>{{ emoji }}</span>
@@ -161,11 +195,11 @@ const emit = defineEmits(['view-profile']);
<!-- Hover Actions -->
<div
v-if="msg.status !== 'failed'"
class="absolute right-4 -top-4 opacity-0 group-hover:opacity-100 transition-opacity z-20 flex gap-1 bg-discord-sidebar border border-white/10 rounded-lg p-1 shadow-xl"
v-if="msg.status !== 'failed' && msg.id"
class="absolute right-4 -top-4 opacity-0 group-hover:opacity-100 transition-all z-20 flex gap-1 bg-discord-sidebar border border-white/10 rounded-xl p-1.5 shadow-2xl"
>
<button
class="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-all"
class="p-2 hover:bg-white/10 rounded-lg text-gray-400 hover:text-white transition-all"
title="Add Reaction"
@click="showEmojiPicker = showEmojiPicker === msg.id ? null : msg.id"
>
@@ -180,7 +214,7 @@ const emit = defineEmits(['view-profile']);
<button
v-for="emoji in EMOJIS"
:key="emoji"
class="hover:scale-125 transition-transform p-1 text-lg"
class="hover:scale-125 transition-transform p-1.5 text-lg hover:bg-white/10 rounded-lg"
@click="toggleReaction(msg.id, emoji)"
>
{{ emoji }}
@@ -191,8 +225,8 @@ const emit = defineEmits(['view-profile']);
</div>
<!-- Input -->
<div class="p-4 bg-discord-dark">
<div class="relative bg-discord-sidebar/50 rounded-xl border border-white/5 p-1 transition-all focus-within:border-violet-500/30 focus-within:bg-discord-sidebar/80">
<div class="p-4 bg-discord-dark border-t border-white/5">
<div class="relative bg-discord-sidebar/50 rounded-xl border border-white/5 p-1 transition-all focus-within:border-violet-500/30 focus-within:bg-discord-sidebar/80 focus-within:shadow-lg focus-within:shadow-violet-500/5">
<input
v-model="newMessage"
type="text"
@@ -201,21 +235,21 @@ const emit = defineEmits(['view-profile']);
@keyup.enter="send"
>
<button
class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-gray-400 hover:text-violet-400 transition-colors"
class="absolute right-2 top-1/2 -translate-y-1/2 p-2.5 text-gray-400 hover:text-violet-400 hover:bg-violet-600/10 rounded-lg transition-all"
@click="send"
>
<Send size="20" />
</button>
</div>
<div class="mt-2 flex items-center gap-4 px-1">
<div class="flex items-center gap-1.5 text-[10px] text-gray-500 uppercase tracking-widest font-bold">
<div class="led led-orange w-1.5 h-1.5" /> Pending
<div class="mt-3 flex items-center gap-6 px-1">
<div class="flex items-center gap-2 text-[10px] text-gray-500 uppercase tracking-widest font-bold">
<div class="led led-orange w-2 h-2 animate-pulse" /> Pending
</div>
<div class="flex items-center gap-1.5 text-[10px] text-gray-500 uppercase tracking-widest font-bold">
<div class="led led-green w-1.5 h-1.5" /> Validated
<div class="flex items-center gap-2 text-[10px] text-gray-500 uppercase tracking-widest font-bold">
<div class="led led-green w-2 h-2" /> Validated
</div>
<div class="flex items-center gap-1.5 text-[10px] text-gray-500 uppercase tracking-widest font-bold">
<div class="led led-red w-1.5 h-1.5" /> Failed
<div class="flex items-center gap-2 text-[10px] text-gray-500 uppercase tracking-widest font-bold">
<div class="led led-red w-2 h-2" /> Failed (not saved)
</div>
</div>
</div>
@@ -231,13 +265,4 @@ const emit = defineEmits(['view-profile']);
0% { transform: scale(0.5); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.animate-fade-in-up {
animation: fade-in-up 0.2s ease-out;
}
@keyframes fade-in-up {
0% { transform: translateY(10px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
</style>