chore: set up pre-commit hooks and fix linting (Task #181)
This commit is contained in:
@@ -7,7 +7,7 @@ import ChatLayout from './components/ChatLayout.vue';
|
||||
const chatStore = useChatStore();
|
||||
const videoRef = ref(null);
|
||||
|
||||
const handleMuteToggle = (isMuted) => {
|
||||
const handleMuteToggle = () => {
|
||||
if (videoRef.value) {
|
||||
// Note: YouTube iframe API would be needed for true control,
|
||||
// but for a simple background video loop, we can't easily unmute a background iframe without user interaction policies.
|
||||
@@ -27,15 +27,18 @@ const handleMuteToggle = (isMuted) => {
|
||||
frameborder="0"
|
||||
allow="autoplay; encrypted-media"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
/>
|
||||
<!-- Overlay gradient -->
|
||||
<div class="absolute inset-0 bg-crypto-dark/60 backdrop-blur-[2px]"></div>
|
||||
<div class="absolute inset-0 bg-crypto-dark/60 backdrop-blur-[2px]" />
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="relative z-10 h-full">
|
||||
<WalletConnect v-if="!chatStore.isConnected" />
|
||||
<ChatLayout v-else @toggleMute="handleMuteToggle" />
|
||||
<ChatLayout
|
||||
v-else
|
||||
@toggle-mute="handleMuteToggle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,9 +3,8 @@ import { useChatStore } from '../stores/chat';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import MessageList from './MessageList.vue';
|
||||
import UserList from './UserList.vue';
|
||||
import UserList from './UserList.vue';
|
||||
import MusicPlayer from './MusicPlayer.vue';
|
||||
import { Hash, Volume2, VolumeX, Settings, X, Coins, Menu, User } from 'lucide-vue-next';
|
||||
import { Hash, Volume2, VolumeX, Settings, X, Menu, User } from 'lucide-vue-next';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const showProfile = ref(false);
|
||||
@@ -40,16 +39,24 @@ const saveSettings = () => {
|
||||
<!-- Mobile Menu Overlay -->
|
||||
<div
|
||||
v-if="showMobileMenu"
|
||||
@click="showMobileMenu = false"
|
||||
class="fixed inset-0 bg-black/60 z-40 md:hidden backdrop-blur-sm transition-opacity"
|
||||
></div>
|
||||
@click="showMobileMenu = false"
|
||||
/>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<div v-if="showSettings" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<div
|
||||
v-if="showSettings"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4"
|
||||
>
|
||||
<div class="bg-discord-sidebar border border-white/10 rounded-2xl w-full max-w-md shadow-2xl animate-pop-in">
|
||||
<div class="p-6 border-b border-white/5 flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold text-white">Profile Settings</h2>
|
||||
<button @click="showSettings = false" class="text-gray-400 hover:text-white transition-colors">
|
||||
<h2 class="text-xl font-bold text-white">
|
||||
Profile Settings
|
||||
</h2>
|
||||
<button
|
||||
class="text-gray-400 hover:text-white transition-colors"
|
||||
@click="showSettings = false"
|
||||
>
|
||||
<X size="24" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -61,7 +68,7 @@ const saveSettings = () => {
|
||||
type="text"
|
||||
class="w-full bg-discord-black border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-violet-500/50 transition-all"
|
||||
placeholder="Enter new username"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-crypto-muted uppercase tracking-wider mb-2">Wallet Address</label>
|
||||
@@ -72,14 +79,14 @@ const saveSettings = () => {
|
||||
</div>
|
||||
<div class="p-6 border-t border-white/5 flex gap-3">
|
||||
<button
|
||||
@click="showSettings = false"
|
||||
class="flex-1 px-4 py-2.5 rounded-xl border border-white/10 text-white font-medium hover:bg-white/5 transition-all"
|
||||
@click="showSettings = false"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveSettings"
|
||||
class="flex-1 px-4 py-2.5 rounded-xl bg-violet-600 text-white font-medium hover:bg-violet-500 shadow-lg shadow-violet-600/20 transition-all"
|
||||
@click="saveSettings"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
@@ -95,32 +102,54 @@ const saveSettings = () => {
|
||||
]"
|
||||
>
|
||||
<div class="h-12 px-4 flex items-center justify-between border-b border-black/20 shadow-sm">
|
||||
<h1 class="font-bold text-white truncate">Plexus Server</h1>
|
||||
<button @click="toggleMute" class="text-gray-400 hover:text-gray-200 transition-colors">
|
||||
<VolumeX v-if="isMuted" size="18" />
|
||||
<Volume2 v-else size="18" />
|
||||
<h1 class="font-bold text-white truncate">
|
||||
Plexus Server
|
||||
</h1>
|
||||
<button
|
||||
class="text-gray-400 hover:text-gray-200 transition-colors"
|
||||
@click="toggleMute"
|
||||
>
|
||||
<VolumeX
|
||||
v-if="isMuted"
|
||||
size="18"
|
||||
/>
|
||||
<Volume2
|
||||
v-else
|
||||
size="18"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto py-3 space-y-0.5 px-2">
|
||||
<!-- Profile Link -->
|
||||
<button
|
||||
@click="selectedProfileAddress = walletAddress; showProfile = true; showMobileMenu = false"
|
||||
:class="['w-full flex items-center gap-2 px-2 py-1.5 rounded-md transition-all group mb-4',
|
||||
showProfile && selectedProfileAddress === walletAddress ? 'bg-[#3f4147] text-white' : 'text-gray-400 hover:bg-[#35373c] hover:text-gray-200']"
|
||||
showProfile && selectedProfileAddress === walletAddress ? 'bg-[#3f4147] text-white' : 'text-gray-400 hover:bg-[#35373c] hover:text-gray-200']"
|
||||
@click="selectedProfileAddress = walletAddress; showProfile = true; showMobileMenu = false"
|
||||
>
|
||||
<User size="18" class="text-violet-400" />
|
||||
<User
|
||||
size="18"
|
||||
class="text-violet-400"
|
||||
/>
|
||||
<span class="text-sm font-medium">My Profile</span>
|
||||
</button>
|
||||
|
||||
<div class="px-2 mb-2 text-[11px] font-bold text-gray-500 uppercase tracking-wider">Text Channels</div>
|
||||
<div v-for="channel in channels" :key="channel.id">
|
||||
<div class="px-2 mb-2 text-[11px] font-bold text-gray-500 uppercase tracking-wider">
|
||||
Text Channels
|
||||
</div>
|
||||
<div
|
||||
v-for="channel in channels"
|
||||
:key="channel.id"
|
||||
>
|
||||
<button
|
||||
@click="chatStore.setChannel(channel.id); showProfile = false; showMobileMenu = false"
|
||||
:class="['w-full flex items-center gap-2 px-2 py-1.5 rounded-md transition-all group',
|
||||
currentChannel === channel.id && !showProfile ? 'bg-[#3f4147] text-white' : 'text-gray-400 hover:bg-[#35373c] hover:text-gray-200']"
|
||||
currentChannel === channel.id && !showProfile ? 'bg-[#3f4147] text-white' : 'text-gray-400 hover:bg-[#35373c] hover:text-gray-200']"
|
||||
@click="chatStore.setChannel(channel.id); showProfile = false; showMobileMenu = false"
|
||||
>
|
||||
<Hash size="18" :class="currentChannel === channel.id && !showProfile ? 'text-gray-200' : 'text-gray-500 group-hover:text-gray-400'" />
|
||||
<Hash
|
||||
size="18"
|
||||
:class="currentChannel === channel.id && !showProfile ? 'text-gray-200' : 'text-gray-500 group-hover:text-gray-400'"
|
||||
/>
|
||||
<span class="text-sm font-medium">{{ channel.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -130,18 +159,28 @@ const saveSettings = () => {
|
||||
<div class="bg-discord-black p-2 space-y-2">
|
||||
<MusicPlayer />
|
||||
|
||||
<div class="flex items-center gap-2 p-1.5 rounded-md hover:bg-[#35373c] transition-all group cursor-pointer" @click="showSettings = true">
|
||||
<div
|
||||
class="flex items-center gap-2 p-1.5 rounded-md hover:bg-[#35373c] transition-all group cursor-pointer"
|
||||
@click="showSettings = true"
|
||||
>
|
||||
<div class="relative">
|
||||
<div class="w-8 h-8 rounded-full bg-violet-600 flex items-center justify-center text-white text-xs font-bold">
|
||||
{{ username?.substring(0, 2).toUpperCase() }}
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-discord-black rounded-full"></div>
|
||||
<div class="absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-discord-black rounded-full" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-xs font-bold text-white truncate">{{ username }}</div>
|
||||
<div class="text-[10px] text-gray-400 truncate">#{{ walletAddress?.slice(-4) }}</div>
|
||||
<div class="text-xs font-bold text-white truncate">
|
||||
{{ username }}
|
||||
</div>
|
||||
<div class="text-[10px] text-gray-400 truncate">
|
||||
#{{ walletAddress?.slice(-4) }}
|
||||
</div>
|
||||
</div>
|
||||
<Settings size="14" class="text-gray-400 group-hover:text-gray-200" />
|
||||
<Settings
|
||||
size="14"
|
||||
class="text-gray-400 group-hover:text-gray-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,19 +190,28 @@ const saveSettings = () => {
|
||||
<!-- Header -->
|
||||
<div class="h-12 px-4 flex items-center border-b border-black/20 shadow-sm bg-discord-dark/95 backdrop-blur-sm z-10">
|
||||
<button
|
||||
@click="showMobileMenu = true"
|
||||
class="md:hidden mr-3 text-gray-400 hover:text-white transition-colors"
|
||||
class="md:hidden mr-3 text-gray-400 hover:text-white transition-colors"
|
||||
@click="showMobileMenu = true"
|
||||
>
|
||||
<Menu size="24" />
|
||||
</button>
|
||||
<Hash size="20" class="text-gray-400 mr-2" />
|
||||
<Hash
|
||||
size="20"
|
||||
class="text-gray-400 mr-2"
|
||||
/>
|
||||
<span class="font-bold text-white mr-4">{{ showProfile ? (selectedProfileAddress === walletAddress ? 'My Profile' : 'User Profile') : currentChannel }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<div class="flex-1 flex flex-col relative overflow-hidden">
|
||||
<UserProfile v-if="showProfile" :address="selectedProfileAddress" />
|
||||
<MessageList v-else @view-profile="(addr) => { selectedProfileAddress = addr; showProfile = true; }" />
|
||||
<UserProfile
|
||||
v-if="showProfile"
|
||||
:address="selectedProfileAddress"
|
||||
/>
|
||||
<MessageList
|
||||
v-else
|
||||
@view-profile="(addr) => { selectedProfileAddress = addr; showProfile = true; }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Member List (Discord Style) -->
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
@@ -59,45 +59,64 @@ const emit = defineEmits(['view-profile']);
|
||||
<!-- Header (Desktop only, mobile header is in ChatLayout) -->
|
||||
<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>
|
||||
|
||||
<!-- Messages -->
|
||||
<div ref="messagesContainer" class="flex-1 overflow-y-auto p-4 space-y-1 scroll-smooth custom-scrollbar">
|
||||
<div
|
||||
ref="messagesContainer"
|
||||
class="flex-1 overflow-y-auto p-4 space-y-1 scroll-smooth custom-scrollbar"
|
||||
>
|
||||
<!-- Beginning of conversation marker -->
|
||||
<div class="py-12 px-4 border-b border-white/5 mb-8">
|
||||
<div class="w-16 h-16 rounded-2xl bg-gradient-to-br from-violet-600 to-indigo-600 flex items-center justify-center text-white mb-4 shadow-xl shadow-violet-600/20">
|
||||
<Hash size="32" />
|
||||
</div>
|
||||
<h2 class="text-3xl font-bold text-white mb-2">Welcome to #{{ currentChannel }}!</h2>
|
||||
<p class="text-gray-400 text-base max-w-md leading-relaxed">This is the very beginning of the <span class="text-violet-400 font-semibold">#{{ currentChannel }}</span> channel. Use this space to connect, share, and grow with the community.</p>
|
||||
<h2 class="text-3xl font-bold text-white mb-2">
|
||||
Welcome to #{{ currentChannel }}!
|
||||
</h2>
|
||||
<p class="text-gray-400 text-base max-w-md leading-relaxed">
|
||||
This is the very beginning of the <span class="text-violet-400 font-semibold">#{{ currentChannel }}</span> channel. Use this space to connect, share, and grow with the community.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-for="(msg, index) in currentMessages" :key="msg.id || msg.tempId"
|
||||
<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"
|
||||
>
|
||||
<!-- Avatar (only if first message in group) -->
|
||||
<div class="w-10 flex-shrink-0">
|
||||
<div v-if="index === 0 || currentMessages[index-1].walletAddress !== msg.walletAddress"
|
||||
@click="emit('view-profile', msg.walletAddress)"
|
||||
<div
|
||||
v-if="index === 0 || currentMessages[index-1].walletAddress !== msg.walletAddress"
|
||||
class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm shadow-lg border border-white/10 mt-1 cursor-pointer hover:opacity-80 transition-opacity"
|
||||
:class="msg.walletAddress === walletAddress ? 'bg-gradient-to-br from-violet-500 to-fuchsia-600' : 'bg-discord-sidebar'"
|
||||
@click="emit('view-profile', msg.walletAddress)"
|
||||
>
|
||||
{{ msg.username?.substring(0, 2).toUpperCase() }}
|
||||
</div>
|
||||
<div v-else class="w-10 text-[10px] text-crypto-muted opacity-0 group-hover:opacity-100 text-right pr-2 pt-1.5 transition-opacity">
|
||||
<div
|
||||
v-else
|
||||
class="w-10 text-[10px] text-crypto-muted opacity-0 group-hover:opacity-100 text-right pr-2 pt-1.5 transition-opacity"
|
||||
>
|
||||
{{ formatTime(msg.timestamp) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<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">
|
||||
<div
|
||||
v-if="index === 0 || currentMessages[index-1].walletAddress !== msg.walletAddress"
|
||||
class="flex items-center gap-2 mb-0.5"
|
||||
>
|
||||
<span
|
||||
@click="emit('view-profile', msg.walletAddress)"
|
||||
:class="['text-sm font-bold hover:underline cursor-pointer', msg.walletAddress === walletAddress ? 'text-violet-400' : 'text-white']"
|
||||
@click="emit('view-profile', msg.walletAddress)"
|
||||
>
|
||||
{{ msg.username }}
|
||||
</span>
|
||||
@@ -113,7 +132,7 @@ const emit = defineEmits(['view-profile']);
|
||||
'led-red': msg.status === 'failed'
|
||||
}"
|
||||
:title="msg.status"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div :class="['text-sm leading-relaxed break-words', msg.status === 'failed' ? 'text-status-failed line-through opacity-60' : 'text-gray-100']">
|
||||
@@ -121,15 +140,18 @@ const emit = defineEmits(['view-profile']);
|
||||
</div>
|
||||
|
||||
<!-- Reactions Display -->
|
||||
<div v-if="msg.reactions && msg.reactions.length > 0" class="flex flex-wrap gap-1 mt-1.5">
|
||||
<div
|
||||
v-if="msg.reactions && msg.reactions.length > 0"
|
||||
class="flex flex-wrap gap-1 mt-1.5"
|
||||
>
|
||||
<button
|
||||
v-for="emoji in getUniqueEmojis(msg.reactions)"
|
||||
:key="emoji"
|
||||
@click="toggleReaction(msg.id, emoji)"
|
||||
:class="['flex items-center gap-1.5 px-2 py-0.5 rounded-lg text-xs border transition-all animate-pop-in',
|
||||
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']"
|
||||
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']"
|
||||
@click="toggleReaction(msg.id, emoji)"
|
||||
>
|
||||
<span>{{ emoji }}</span>
|
||||
<span class="font-bold">{{ getReactionCount(msg.reactions, emoji) }}</span>
|
||||
@@ -138,22 +160,28 @@ const emit = defineEmits(['view-profile']);
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<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"
|
||||
>
|
||||
<button
|
||||
@click="showEmojiPicker = showEmojiPicker === msg.id ? null : msg.id"
|
||||
class="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-all"
|
||||
title="Add Reaction"
|
||||
@click="showEmojiPicker = showEmojiPicker === msg.id ? null : msg.id"
|
||||
>
|
||||
<Smile size="16" />
|
||||
</button>
|
||||
|
||||
<!-- Emoji Picker Popover -->
|
||||
<div v-if="showEmojiPicker === msg.id" class="absolute right-0 bottom-full mb-2 bg-discord-sidebar border border-white/10 rounded-xl p-2 shadow-2xl flex gap-1 z-30 animate-pop-in">
|
||||
<div
|
||||
v-if="showEmojiPicker === msg.id"
|
||||
class="absolute right-0 bottom-full mb-2 bg-discord-sidebar border border-white/10 rounded-xl p-2 shadow-2xl flex gap-1 z-30 animate-pop-in"
|
||||
>
|
||||
<button
|
||||
v-for="emoji in EMOJIS"
|
||||
:key="emoji"
|
||||
@click="toggleReaction(msg.id, emoji)"
|
||||
class="hover:scale-125 transition-transform p-1 text-lg"
|
||||
@click="toggleReaction(msg.id, emoji)"
|
||||
>
|
||||
{{ emoji }}
|
||||
</button>
|
||||
@@ -167,27 +195,27 @@ const emit = defineEmits(['view-profile']);
|
||||
<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">
|
||||
<input
|
||||
v-model="newMessage"
|
||||
@keyup.enter="send"
|
||||
type="text"
|
||||
:placeholder="`Message #${currentChannel}`"
|
||||
type="text"
|
||||
:placeholder="`Message #${currentChannel}`"
|
||||
class="w-full bg-transparent text-white placeholder-gray-500 py-3 pl-4 pr-12 focus:outline-none"
|
||||
/>
|
||||
@keyup.enter="send"
|
||||
>
|
||||
<button
|
||||
@click="send"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-gray-400 hover:text-violet-400 transition-colors"
|
||||
@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"></div> Pending
|
||||
<div class="led led-orange w-1.5 h-1.5" /> 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"></div> Validated
|
||||
<div class="led led-green w-1.5 h-1.5" /> 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"></div> Failed
|
||||
<div class="led led-red w-1.5 h-1.5" /> Failed
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,41 +51,66 @@ const updateVolume = () => {
|
||||
class="hidden"
|
||||
:src="`https://www.youtube.com/embed/${LOFI_VIDEOS[currentVideoIndex]}?enablejsapi=1&autoplay=0&controls=0&disablekb=1&fs=0&modestbranding=1&iv_load_policy=3`"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
/>
|
||||
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center text-white shadow-lg animate-pulse-slow">
|
||||
<Music size="24" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-bold text-white truncate">Lofi Radio</div>
|
||||
<div class="text-[10px] text-crypto-muted uppercase tracking-wider">Chilling in the Nebula</div>
|
||||
<div class="text-sm font-bold text-white truncate">
|
||||
Lofi Radio
|
||||
</div>
|
||||
<div class="text-[10px] text-crypto-muted uppercase tracking-wider">
|
||||
Chilling in the Nebula
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="togglePlay" class="p-2 bg-white/10 hover:bg-white/20 rounded-full text-white transition-all">
|
||||
<Pause v-if="isPlaying" size="18" />
|
||||
<Play v-else size="18" />
|
||||
<button
|
||||
class="p-2 bg-white/10 hover:bg-white/20 rounded-full text-white transition-all"
|
||||
@click="togglePlay"
|
||||
>
|
||||
<Pause
|
||||
v-if="isPlaying"
|
||||
size="18"
|
||||
/>
|
||||
<Play
|
||||
v-else
|
||||
size="18"
|
||||
/>
|
||||
</button>
|
||||
<button @click="nextTrack" class="p-2 hover:bg-white/10 rounded-full text-gray-400 hover:text-white transition-all">
|
||||
<button
|
||||
class="p-2 hover:bg-white/10 rounded-full text-gray-400 hover:text-white transition-all"
|
||||
@click="nextTrack"
|
||||
>
|
||||
<SkipForward size="18" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center gap-3">
|
||||
<button @click="toggleMute" class="text-gray-400 hover:text-white transition-colors">
|
||||
<VolumeX v-if="isMuted || volume == 0" size="16" />
|
||||
<Volume2 v-else size="16" />
|
||||
<button
|
||||
class="text-gray-400 hover:text-white transition-colors"
|
||||
@click="toggleMute"
|
||||
>
|
||||
<VolumeX
|
||||
v-if="isMuted || volume == 0"
|
||||
size="16"
|
||||
/>
|
||||
<Volume2
|
||||
v-else
|
||||
size="16"
|
||||
/>
|
||||
</button>
|
||||
<input
|
||||
v-model="volume"
|
||||
@input="updateVolume"
|
||||
type="range"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
class="flex-1 h-1 bg-white/10 rounded-lg appearance-none cursor-pointer accent-indigo-500"
|
||||
/>
|
||||
class="flex-1 h-1 bg-white/10 rounded-lg appearance-none cursor-pointer accent-indigo-500"
|
||||
@input="updateVolume"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,19 +13,21 @@ const emit = defineEmits(['view-profile']);
|
||||
<div class="flex-1 overflow-y-auto p-3 space-y-6">
|
||||
<!-- Online Users -->
|
||||
<div v-if="onlineUsers.length > 0">
|
||||
<h3 class="px-2 mb-2 text-[11px] font-bold text-gray-500 uppercase tracking-wider">Online — {{ onlineUsers.length }}</h3>
|
||||
<h3 class="px-2 mb-2 text-[11px] font-bold text-gray-500 uppercase tracking-wider">
|
||||
Online — {{ onlineUsers.length }}
|
||||
</h3>
|
||||
<div class="space-y-0.5">
|
||||
<div
|
||||
v-for="user in onlineUsers"
|
||||
:key="user.wallet_address"
|
||||
@click="emit('view-profile', user.wallet_address)"
|
||||
class="flex items-center gap-2.5 px-2 py-1.5 rounded-md hover:bg-[#35373c] cursor-pointer group transition-all"
|
||||
@click="emit('view-profile', user.wallet_address)"
|
||||
>
|
||||
<div class="relative">
|
||||
<div class="w-8 h-8 rounded-full bg-violet-600 flex items-center justify-center text-xs font-bold text-white shadow-sm">
|
||||
{{ user.username.substring(0, 2).toUpperCase() }}
|
||||
</div>
|
||||
<div class="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-green-500 border-2 border-[#2b2d31] rounded-full"></div>
|
||||
<div class="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-green-500 border-2 border-[#2b2d31] rounded-full" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-300 truncate group-hover:text-white transition-colors">
|
||||
@@ -38,19 +40,21 @@ const emit = defineEmits(['view-profile']);
|
||||
|
||||
<!-- Offline Users -->
|
||||
<div v-if="offlineUsers.length > 0">
|
||||
<h3 class="px-2 mb-2 text-[11px] font-bold text-gray-500 uppercase tracking-wider">Offline — {{ offlineUsers.length }}</h3>
|
||||
<h3 class="px-2 mb-2 text-[11px] font-bold text-gray-500 uppercase tracking-wider">
|
||||
Offline — {{ offlineUsers.length }}
|
||||
</h3>
|
||||
<div class="space-y-0.5">
|
||||
<div
|
||||
v-for="user in offlineUsers"
|
||||
:key="user.wallet_address"
|
||||
@click="emit('view-profile', user.wallet_address)"
|
||||
class="flex items-center gap-2.5 px-2 py-1.5 rounded-md hover:bg-[#35373c] cursor-pointer group transition-all opacity-60 hover:opacity-100"
|
||||
@click="emit('view-profile', user.wallet_address)"
|
||||
>
|
||||
<div class="relative">
|
||||
<div class="w-8 h-8 rounded-full bg-[#3f4147] flex items-center justify-center text-xs font-bold text-gray-500">
|
||||
{{ user.username.substring(0, 2).toUpperCase() }}
|
||||
</div>
|
||||
<div class="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-gray-600 border-2 border-[#2b2d31] rounded-full"></div>
|
||||
<div class="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-gray-600 border-2 border-[#2b2d31] rounded-full" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-500 truncate group-hover:text-gray-400">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useChatStore } from '../stores/chat';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { User, MessageSquare, Calendar, MapPin, Link as LinkIcon, Edit3, Send, X } from 'lucide-vue-next';
|
||||
import { MessageSquare, Calendar, Edit3, Send, X } from 'lucide-vue-next';
|
||||
|
||||
const props = defineProps({
|
||||
address: {
|
||||
@@ -12,7 +12,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const chatStore = useChatStore();
|
||||
const { profileUser, profilePosts, isProfileLoading, walletAddress, username } = storeToRefs(chatStore);
|
||||
const { profileUser, profilePosts, isProfileLoading, walletAddress } = storeToRefs(chatStore);
|
||||
|
||||
const isEditing = ref(false);
|
||||
const editBio = ref('');
|
||||
@@ -51,11 +51,17 @@ const formatTime = (isoString) => {
|
||||
|
||||
<template>
|
||||
<div class="flex-1 flex flex-col h-full bg-discord-dark overflow-y-auto custom-scrollbar">
|
||||
<div v-if="isProfileLoading" class="flex-1 flex items-center justify-center">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-violet-500"></div>
|
||||
<div
|
||||
v-if="isProfileLoading"
|
||||
class="flex-1 flex items-center justify-center"
|
||||
>
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-violet-500" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="profileUser" class="flex flex-col">
|
||||
<div
|
||||
v-else-if="profileUser"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<!-- Banner -->
|
||||
<div
|
||||
class="h-48 w-full relative transition-colors duration-500"
|
||||
@@ -72,13 +78,17 @@ const formatTime = (isoString) => {
|
||||
<div class="mt-20 px-8 pb-8 border-b border-white/5">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-white">{{ profileUser.username }}</h1>
|
||||
<p class="text-gray-400 font-mono text-sm mt-1">{{ profileUser.wallet_address }}</p>
|
||||
<h1 class="text-3xl font-bold text-white">
|
||||
{{ profileUser.username }}
|
||||
</h1>
|
||||
<p class="text-gray-400 font-mono text-sm mt-1">
|
||||
{{ profileUser.wallet_address }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
v-if="profileUser.wallet_address === walletAddress"
|
||||
@click="startEditing"
|
||||
class="px-4 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-full text-white text-sm font-medium transition-all flex items-center gap-2"
|
||||
@click="startEditing"
|
||||
>
|
||||
<Edit3 size="16" /> Edit Profile
|
||||
</button>
|
||||
@@ -98,20 +108,26 @@ const formatTime = (isoString) => {
|
||||
<!-- Wall / Posts -->
|
||||
<div class="p-8 max-w-3xl">
|
||||
<h2 class="text-xl font-bold text-white mb-6 flex items-center gap-2">
|
||||
<MessageSquare size="20" class="text-violet-400" /> Wall
|
||||
<MessageSquare
|
||||
size="20"
|
||||
class="text-violet-400"
|
||||
/> Wall
|
||||
</h2>
|
||||
|
||||
<!-- New Post Input (only for owner) -->
|
||||
<div v-if="profileUser.wallet_address === walletAddress" class="mb-8 bg-discord-sidebar/30 rounded-2xl p-4 border border-white/5">
|
||||
<div
|
||||
v-if="profileUser.wallet_address === walletAddress"
|
||||
class="mb-8 bg-discord-sidebar/30 rounded-2xl p-4 border border-white/5"
|
||||
>
|
||||
<textarea
|
||||
v-model="newPostContent"
|
||||
placeholder="What's on your mind?"
|
||||
class="w-full bg-transparent text-white placeholder-gray-500 resize-none focus:outline-none min-h-[100px]"
|
||||
></textarea>
|
||||
/>
|
||||
<div class="flex justify-end mt-2 pt-2 border-t border-white/5">
|
||||
<button
|
||||
@click="submitPost"
|
||||
class="px-6 py-2 bg-violet-600 hover:bg-violet-500 text-white rounded-full font-bold transition-all flex items-center gap-2 shadow-lg shadow-violet-600/20"
|
||||
@click="submitPost"
|
||||
>
|
||||
Post <Send size="16" />
|
||||
</button>
|
||||
@@ -120,7 +136,10 @@ const formatTime = (isoString) => {
|
||||
|
||||
<!-- Posts List -->
|
||||
<div class="space-y-6">
|
||||
<div v-if="profilePosts.length === 0" class="text-center py-12 text-gray-500 italic">
|
||||
<div
|
||||
v-if="profilePosts.length === 0"
|
||||
class="text-center py-12 text-gray-500 italic"
|
||||
>
|
||||
No posts yet.
|
||||
</div>
|
||||
<div
|
||||
@@ -134,8 +153,12 @@ const formatTime = (isoString) => {
|
||||
{{ profileUser.username?.substring(0, 2).toUpperCase() }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-white">{{ profileUser.username }}</div>
|
||||
<div class="text-xs text-gray-500">{{ formatTime(post.timestamp) }}</div>
|
||||
<div class="font-bold text-white">
|
||||
{{ profileUser.username }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{{ formatTime(post.timestamp) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -148,11 +171,19 @@ const formatTime = (isoString) => {
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div v-if="isEditing" class="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<div
|
||||
v-if="isEditing"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4"
|
||||
>
|
||||
<div class="bg-discord-sidebar border border-white/10 rounded-2xl w-full max-w-lg shadow-2xl animate-pop-in">
|
||||
<div class="p-6 border-b border-white/5 flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold text-white">Edit Profile</h2>
|
||||
<button @click="isEditing = false" class="text-gray-400 hover:text-white transition-colors">
|
||||
<h2 class="text-xl font-bold text-white">
|
||||
Edit Profile
|
||||
</h2>
|
||||
<button
|
||||
class="text-gray-400 hover:text-white transition-colors"
|
||||
@click="isEditing = false"
|
||||
>
|
||||
<X size="24" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -163,7 +194,7 @@ const formatTime = (isoString) => {
|
||||
v-model="editBio"
|
||||
class="w-full bg-discord-black border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:ring-2 focus:ring-violet-500/50 transition-all min-h-[120px]"
|
||||
placeholder="Tell us about yourself..."
|
||||
></textarea>
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">Banner Color</label>
|
||||
@@ -172,25 +203,25 @@ const formatTime = (isoString) => {
|
||||
v-model="editBannerColor"
|
||||
type="color"
|
||||
class="w-12 h-12 rounded-lg bg-transparent border-none cursor-pointer"
|
||||
/>
|
||||
>
|
||||
<input
|
||||
v-model="editBannerColor"
|
||||
type="text"
|
||||
class="flex-1 bg-discord-black border border-white/10 rounded-xl px-4 py-3 text-white font-mono"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 border-t border-white/5 flex gap-3">
|
||||
<button
|
||||
@click="isEditing = false"
|
||||
class="flex-1 px-4 py-2.5 rounded-xl border border-white/10 text-white font-medium hover:bg-white/5 transition-all"
|
||||
@click="isEditing = false"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveProfile"
|
||||
class="flex-1 px-4 py-2.5 rounded-xl bg-violet-600 text-white font-medium hover:bg-violet-500 shadow-lg shadow-violet-600/20 transition-all"
|
||||
@click="saveProfile"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useChatStore } from '../stores/chat';
|
||||
|
||||
const chatStore = useChatStore();
|
||||
@@ -42,19 +42,28 @@ const connectWallet = async () => {
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-screen bg-black/50 backdrop-blur-sm">
|
||||
<div class="p-8 bg-crypto-panel rounded-xl shadow-2xl border border-crypto-accent/20 text-center max-w-md w-full">
|
||||
<h1 class="text-3xl font-bold mb-2 bg-gradient-to-r from-purple-400 to-pink-600 text-transparent bg-clip-text">Crypto Chat</h1>
|
||||
<p class="text-crypto-muted mb-8">Connect your wallet to join the conversation.</p>
|
||||
<h1 class="text-3xl font-bold mb-2 bg-gradient-to-r from-purple-400 to-pink-600 text-transparent bg-clip-text">
|
||||
Crypto Chat
|
||||
</h1>
|
||||
<p class="text-crypto-muted mb-8">
|
||||
Connect your wallet to join the conversation.
|
||||
</p>
|
||||
|
||||
<button
|
||||
@click="connectWallet"
|
||||
:disabled="isConnecting"
|
||||
:disabled="isConnecting"
|
||||
class="w-full py-3 px-6 bg-crypto-accent hover:bg-violet-600 text-white rounded-lg font-semibold transition-all transform hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
@click="connectWallet"
|
||||
>
|
||||
<span v-if="isConnecting">Connecting...</span>
|
||||
<span v-else>Connect Phantom Wallet</span>
|
||||
</button>
|
||||
|
||||
<p v-if="error" class="mt-4 text-red-400 text-sm">{{ error }}</p>
|
||||
<p
|
||||
v-if="error"
|
||||
class="mt-4 text-red-400 text-sm"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { io } from 'socket.io-client';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
Reference in New Issue
Block a user