This commit is contained in:
201
client/src/components/UserProfile.vue
Normal file
201
client/src/components/UserProfile.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<script setup>
|
||||
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';
|
||||
|
||||
const props = defineProps({
|
||||
address: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const chatStore = useChatStore();
|
||||
const { profileUser, profilePosts, isProfileLoading, walletAddress, username } = storeToRefs(chatStore);
|
||||
|
||||
const isEditing = ref(false);
|
||||
const editBio = ref('');
|
||||
const editBannerColor = ref('');
|
||||
const newPostContent = ref('');
|
||||
|
||||
const loadProfile = () => {
|
||||
chatStore.getProfile(props.address);
|
||||
};
|
||||
|
||||
onMounted(loadProfile);
|
||||
watch(() => props.address, loadProfile);
|
||||
|
||||
const startEditing = () => {
|
||||
editBio.value = profileUser.value.bio || '';
|
||||
editBannerColor.value = profileUser.value.banner_color || '#6366f1';
|
||||
isEditing.value = true;
|
||||
};
|
||||
|
||||
const saveProfile = () => {
|
||||
chatStore.updateProfile(editBio.value, editBannerColor.value);
|
||||
isEditing.value = false;
|
||||
};
|
||||
|
||||
const submitPost = () => {
|
||||
if (!newPostContent.value.trim()) return;
|
||||
chatStore.createPost(newPostContent.value);
|
||||
newPostContent.value = '';
|
||||
};
|
||||
|
||||
const formatTime = (isoString) => {
|
||||
const date = new Date(isoString);
|
||||
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
||||
<div v-else-if="profileUser" class="flex flex-col">
|
||||
<!-- Banner -->
|
||||
<div
|
||||
class="h-48 w-full relative transition-colors duration-500"
|
||||
:style="{ backgroundColor: profileUser.banner_color }"
|
||||
>
|
||||
<div class="absolute -bottom-16 left-8">
|
||||
<div class="w-32 h-32 rounded-full border-8 border-discord-dark bg-violet-600 flex items-center justify-center text-white text-4xl font-bold shadow-xl">
|
||||
{{ profileUser.username?.substring(0, 2).toUpperCase() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile Info -->
|
||||
<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>
|
||||
</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"
|
||||
>
|
||||
<Edit3 size="16" /> Edit Profile
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-gray-200 text-lg leading-relaxed max-w-2xl">
|
||||
{{ profileUser.bio || 'No bio yet...' }}
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-4 text-gray-400 text-sm">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Calendar size="16" /> Joined {{ new Date(profileUser.last_seen).toLocaleDateString() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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
|
||||
</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">
|
||||
<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"
|
||||
>
|
||||
Post <Send size="16" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Posts List -->
|
||||
<div class="space-y-6">
|
||||
<div v-if="profilePosts.length === 0" class="text-center py-12 text-gray-500 italic">
|
||||
No posts yet.
|
||||
</div>
|
||||
<div
|
||||
v-for="post in profilePosts"
|
||||
:key="post.id"
|
||||
class="bg-discord-sidebar/20 rounded-2xl p-6 border border-white/5 hover:border-white/10 transition-all group"
|
||||
>
|
||||
<div class="flex justify-between items-start mb-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-full bg-violet-600/20 flex items-center justify-center text-violet-400 font-bold">
|
||||
{{ 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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-gray-200 leading-relaxed">
|
||||
{{ post.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 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">
|
||||
<X size="24" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 space-y-6">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">Bio</label>
|
||||
<textarea
|
||||
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>
|
||||
<div class="flex gap-3">
|
||||
<input
|
||||
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"
|
||||
>
|
||||
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"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user