This commit is contained in:
@@ -3,12 +3,13 @@ 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 TokenCreator from './TokenCreator.vue';
|
||||
import { Hash, Volume2, VolumeX, Settings, X, Coins, Menu } from 'lucide-vue-next';
|
||||
import { Hash, Volume2, VolumeX, Settings, X, Coins, Menu, User } from 'lucide-vue-next';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const showTokenCreator = ref(false);
|
||||
const showProfile = ref(false);
|
||||
const selectedProfileAddress = ref(null);
|
||||
const showMobileMenu = ref(false);
|
||||
|
||||
const chatStore = useChatStore();
|
||||
@@ -102,24 +103,24 @@ const saveSettings = () => {
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto py-3 space-y-0.5 px-2">
|
||||
<!-- Token Creator Link -->
|
||||
<!-- Profile Link -->
|
||||
<button
|
||||
@click="showTokenCreator = true; showMobileMenu = false"
|
||||
@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',
|
||||
showTokenCreator ? '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']"
|
||||
>
|
||||
<Coins size="18" class="text-violet-400" />
|
||||
<span class="text-sm font-medium">Token Creator</span>
|
||||
<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">
|
||||
<button
|
||||
@click="chatStore.setChannel(channel.id); showTokenCreator = false; showMobileMenu = false"
|
||||
@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 && !showTokenCreator ? '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']"
|
||||
>
|
||||
<Hash size="18" :class="currentChannel === channel.id && !showTokenCreator ? '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>
|
||||
@@ -156,18 +157,18 @@ const saveSettings = () => {
|
||||
<Menu size="24" />
|
||||
</button>
|
||||
<Hash size="20" class="text-gray-400 mr-2" />
|
||||
<span class="font-bold text-white mr-4">{{ showTokenCreator ? 'Token Creator' : currentChannel }}</span>
|
||||
<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">
|
||||
<TokenCreator v-if="showTokenCreator" @back="showTokenCreator = false" />
|
||||
<MessageList v-else />
|
||||
<UserProfile v-if="showProfile" :address="selectedProfileAddress" />
|
||||
<MessageList v-else @view-profile="(addr) => { selectedProfileAddress = addr; showProfile = true; }" />
|
||||
</div>
|
||||
|
||||
<!-- Member List (Discord Style) -->
|
||||
<div class="w-60 bg-discord-sidebar border-l border-black/20 hidden xl:flex flex-col">
|
||||
<UserList />
|
||||
<UserList @view-profile="(addr) => { selectedProfileAddress = addr; showProfile = true; }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,8 @@ const send = () => {
|
||||
const formatTime = (isoString) => {
|
||||
return new Date(isoString).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
};
|
||||
|
||||
const emit = defineEmits(['view-profile']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -79,7 +81,8 @@ const formatTime = (isoString) => {
|
||||
<!-- 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"
|
||||
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"
|
||||
@click="emit('view-profile', 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'"
|
||||
>
|
||||
{{ msg.username?.substring(0, 2).toUpperCase() }}
|
||||
@@ -92,7 +95,10 @@ const formatTime = (isoString) => {
|
||||
<!-- 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">
|
||||
<span :class="['text-sm font-bold hover:underline cursor-pointer', msg.walletAddress === walletAddress ? 'text-violet-400' : 'text-white']">
|
||||
<span
|
||||
@click="emit('view-profile', msg.walletAddress)"
|
||||
:class="['text-sm font-bold hover:underline cursor-pointer', msg.walletAddress === walletAddress ? 'text-violet-400' : 'text-white']"
|
||||
>
|
||||
{{ msg.username }}
|
||||
</span>
|
||||
<span class="text-[10px] text-crypto-muted">{{ formatTime(msg.timestamp) }}</span>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { storeToRefs } from 'pinia';
|
||||
|
||||
const chatStore = useChatStore();
|
||||
const { onlineUsers, offlineUsers } = storeToRefs(chatStore);
|
||||
|
||||
const emit = defineEmits(['view-profile']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -13,7 +15,12 @@ const { onlineUsers, offlineUsers } = storeToRefs(chatStore);
|
||||
<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>
|
||||
<div class="space-y-0.5">
|
||||
<div v-for="user in onlineUsers" :key="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">
|
||||
<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"
|
||||
>
|
||||
<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() }}
|
||||
@@ -33,7 +40,12 @@ const { onlineUsers, offlineUsers } = storeToRefs(chatStore);
|
||||
<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>
|
||||
<div class="space-y-0.5">
|
||||
<div v-for="user in offlineUsers" :key="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">
|
||||
<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"
|
||||
>
|
||||
<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() }}
|
||||
|
||||
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>
|
||||
@@ -15,6 +15,11 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const users = ref([]);
|
||||
const channels = ref([]);
|
||||
|
||||
// Profile state
|
||||
const profileUser = ref(null);
|
||||
const profilePosts = ref([]);
|
||||
const isProfileLoading = ref(false);
|
||||
|
||||
const onlineUsers = computed(() => users.value.filter(u => u.online));
|
||||
const offlineUsers = computed(() => users.value.filter(u => !u.online));
|
||||
|
||||
@@ -74,6 +79,22 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
});
|
||||
|
||||
socket.value.on('profileData', (data) => {
|
||||
profileUser.value = data;
|
||||
profilePosts.value = data.posts;
|
||||
isProfileLoading.value = false;
|
||||
});
|
||||
|
||||
socket.value.on('profileUpdated', (data) => {
|
||||
if (profileUser.value && profileUser.value.wallet_address === walletAddress.value) {
|
||||
profileUser.value = { ...profileUser.value, ...data };
|
||||
}
|
||||
});
|
||||
|
||||
socket.value.on('postCreated', (post) => {
|
||||
profilePosts.value = [post, ...profilePosts.value];
|
||||
});
|
||||
|
||||
socket.value.on('usernameUpdated', ({ username: newName }) => {
|
||||
username.value = newName;
|
||||
const savedAuth = Cookies.get('plexus_auth');
|
||||
@@ -171,6 +192,29 @@ export const useChatStore = defineStore('chat', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function getProfile(address) {
|
||||
if (!socket.value) return;
|
||||
isProfileLoading.value = true;
|
||||
socket.value.emit('getProfile', address);
|
||||
}
|
||||
|
||||
function updateProfile(bio, bannerColor) {
|
||||
if (!socket.value) return;
|
||||
socket.value.emit('updateProfile', {
|
||||
walletAddress: walletAddress.value,
|
||||
bio,
|
||||
bannerColor
|
||||
});
|
||||
}
|
||||
|
||||
function createPost(content) {
|
||||
if (!socket.value || !content.trim()) return;
|
||||
socket.value.emit('createPost', {
|
||||
walletAddress: walletAddress.value,
|
||||
content
|
||||
});
|
||||
}
|
||||
|
||||
function setChannel(channelId) {
|
||||
currentChannel.value = channelId;
|
||||
if (!messages.value[channelId]) {
|
||||
@@ -209,10 +253,16 @@ export const useChatStore = defineStore('chat', () => {
|
||||
onlineUsers,
|
||||
offlineUsers,
|
||||
currentMessages,
|
||||
profileUser,
|
||||
profilePosts,
|
||||
isProfileLoading,
|
||||
connect,
|
||||
sendMessage,
|
||||
toggleReaction,
|
||||
updateUsername,
|
||||
getProfile,
|
||||
updateProfile,
|
||||
createPost,
|
||||
setChannel
|
||||
};
|
||||
});
|
||||
|
||||
26
server/db.js
26
server/db.js
@@ -11,6 +11,8 @@ con.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
wallet_address VARCHAR PRIMARY KEY,
|
||||
username VARCHAR UNIQUE,
|
||||
bio VARCHAR DEFAULT '',
|
||||
banner_color VARCHAR DEFAULT '#6366f1',
|
||||
last_seen TIMESTAMP
|
||||
);
|
||||
|
||||
@@ -31,8 +33,17 @@ con.exec(`
|
||||
PRIMARY KEY (message_id, wallet_address, emoji),
|
||||
FOREIGN KEY (wallet_address) REFERENCES users(wallet_address)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
wallet_address VARCHAR,
|
||||
content VARCHAR,
|
||||
timestamp TIMESTAMP,
|
||||
FOREIGN KEY (wallet_address) REFERENCES users(wallet_address)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS seq_msg_id START 1;
|
||||
CREATE SEQUENCE IF NOT EXISTS seq_post_id START 1;
|
||||
|
||||
-- Migration: Add tx_id to messages if it doesn't exist (for existing DBs)
|
||||
PRAGMA table_info('messages');
|
||||
@@ -46,7 +57,20 @@ con.exec(`
|
||||
if (!hasTxId) {
|
||||
con.run("ALTER TABLE messages ADD COLUMN tx_id VARCHAR", (err) => {
|
||||
if (err) console.error("Error adding tx_id column:", err);
|
||||
else console.log("Added tx_id column to messages table");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Migration: Add bio and banner_color to users
|
||||
con.all("PRAGMA table_info('users')", (err, rows) => {
|
||||
if (err) return;
|
||||
const hasBio = rows.some(r => r.name === 'bio');
|
||||
if (!hasBio) {
|
||||
con.run("ALTER TABLE users ADD COLUMN bio VARCHAR DEFAULT ''", (err) => {
|
||||
if (err) console.error("Error adding bio column:", err);
|
||||
});
|
||||
con.run("ALTER TABLE users ADD COLUMN banner_color VARCHAR DEFAULT '#6366f1'", (err) => {
|
||||
if (err) console.error("Error adding banner_color column:", err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -51,11 +51,13 @@ io.on('connection', (socket) => {
|
||||
|
||||
if (rows.length > 0) {
|
||||
// User exists, update last seen
|
||||
const existingUsername = rows[0].username;
|
||||
con.prepare(`UPDATE users SET last_seen = ? WHERE wallet_address = ?`, (err, uStmt) => {
|
||||
if (err) return console.error("Prepare error:", err);
|
||||
uStmt.run(now, walletAddress, (err) => {
|
||||
uStmt.finalize();
|
||||
if (err) console.error("Update error:", err);
|
||||
socket.emit('usernameUpdated', { username: existingUsername });
|
||||
broadcastUserList();
|
||||
});
|
||||
});
|
||||
@@ -77,6 +79,7 @@ io.on('connection', (socket) => {
|
||||
iStmt.run(walletAddress, finalUsername, now, (err) => {
|
||||
iStmt.finalize();
|
||||
if (err) console.error("Insert error:", err);
|
||||
socket.emit('usernameUpdated', { username: finalUsername });
|
||||
broadcastUserList();
|
||||
});
|
||||
});
|
||||
@@ -165,6 +168,61 @@ io.on('connection', (socket) => {
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('getProfile', (walletAddress) => {
|
||||
console.log(`Fetching profile for ${walletAddress}`);
|
||||
con.prepare(`SELECT wallet_address, username, bio, banner_color, last_seen FROM users WHERE wallet_address = ?`, (err, stmt) => {
|
||||
if (err) return socket.emit('error', { message: 'Database error' });
|
||||
stmt.all(walletAddress, (err, rows) => {
|
||||
stmt.finalize();
|
||||
if (err || rows.length === 0) return socket.emit('error', { message: 'User not found' });
|
||||
|
||||
const user = rows[0];
|
||||
// Fetch posts
|
||||
con.prepare(`SELECT * FROM posts WHERE wallet_address = ? ORDER BY timestamp DESC LIMIT 50`, (err, pStmt) => {
|
||||
if (err) return socket.emit('profileData', { ...user, posts: [] });
|
||||
pStmt.all(walletAddress, (err, posts) => {
|
||||
pStmt.finalize();
|
||||
socket.emit('profileData', { ...user, posts: posts || [] });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('updateProfile', ({ walletAddress, bio, bannerColor }) => {
|
||||
console.log(`Updating profile for ${walletAddress}`);
|
||||
con.prepare(`UPDATE users SET bio = ?, banner_color = ? WHERE wallet_address = ?`, (err, stmt) => {
|
||||
if (err) return socket.emit('error', { message: 'Database error' });
|
||||
stmt.run(bio, bannerColor, walletAddress, (err) => {
|
||||
stmt.finalize();
|
||||
if (err) return socket.emit('error', { message: 'Failed to update profile' });
|
||||
socket.emit('profileUpdated', { bio, bannerColor });
|
||||
broadcastUserList();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('createPost', ({ walletAddress, content }) => {
|
||||
if (!content || content.trim() === '') return;
|
||||
console.log(`New post from ${walletAddress}: ${content}`);
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
con.prepare(`INSERT INTO posts (id, wallet_address, content, timestamp) VALUES (nextval('seq_post_id'), ?, ?, ?)`, (err, stmt) => {
|
||||
if (err) return socket.emit('error', { message: 'Database error' });
|
||||
stmt.run(walletAddress, content, timestamp, (err) => {
|
||||
stmt.finalize();
|
||||
if (err) return socket.emit('error', { message: 'Failed to create post' });
|
||||
|
||||
// Fetch all posts to broadcast update or just emit the new one
|
||||
// For simplicity, we'll just tell the user it was created
|
||||
socket.emit('postCreated', { content, timestamp });
|
||||
|
||||
// If we want a live feed, we could broadcast to a "profile room"
|
||||
// For now, the user can just refresh or we emit to them
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('toggleReaction', ({ messageId, walletAddress, emoji }) => {
|
||||
console.log(`Toggling reaction: ${emoji} on message ${messageId} by ${walletAddress}`);
|
||||
con.prepare(`SELECT * FROM reactions WHERE message_id = ? AND wallet_address = ? AND emoji = ?`, (err, stmt) => {
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user