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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user