-
کاربری یافت نشد
+
+
mdi-account-search
+
کاربری یافت نشد
+
+ کاربری با این نام یا ایمیل در سیستم وجود ندارد
+
+
+
+
+
mdi-account-search
+
جستجوی کاربر
+
+ نام یا ایمیل کاربر مورد نظر را وارد کنید
+
-
- لغو
+
+
+
+ لغو
+
-
-
@@ -673,6 +932,15 @@ export default defineComponent({
const creatingChannel = ref(false);
const channelSearch = ref('');
+ // Pagination state
+ const pagination = ref({
+ limit: 30,
+ offset: 0,
+ hasMore: true,
+ loadingMore: false,
+ totalMessages: 0
+ });
+
// Emoji picker
const showEmojiPicker = ref(false);
const commonEmojis = ref([
@@ -693,6 +961,20 @@ export default defineComponent({
'💟', '❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎'
]);
+ // Quote/Reply functionality
+ const quotedMessage = ref(null);
+ const showMessageActions = ref(null);
+
+ // Like/Dislike functionality
+ const currentUser = ref({
+ id: 1,
+ fullName: 'کاربر فعلی',
+ email: 'user@example.com'
+ });
+
+ // Loading states for reactions
+ const reactingMessages = ref(new Set());
+
// Member management
const showMemberDialog = ref(false);
const showAddMemberDialog = ref(false);
@@ -706,15 +988,6 @@ export default defineComponent({
const autoRefreshInterval = ref(null);
const refreshInterval = 3000; // 3 seconds
- // Current user (mock for now)
- const currentUser = ref({
- id: 1,
- fullName: 'کاربر فعلی',
- email: 'user@example.com'
- });
-
-
-
// New channel form
const newChannel = ref({
name: '',
@@ -822,11 +1095,22 @@ export default defineComponent({
const selectChannel = async (channel) => {
currentChannel.value = channel;
showChannelDrawer.value = false;
+
+ // Reset pagination when switching channels
+ pagination.value = {
+ limit: 30,
+ offset: 0,
+ hasMore: true,
+ loadingMore: false,
+ totalMessages: 0
+ };
+
await loadChannelMessages(channel.channelId);
+
+ // Update channel stats when selecting a channel
+ await updateChannelStats();
};
-
-
const joinChannel = async (channel) => {
try {
const response = await axios.post(`/api/chat/channels/${channel.channelId}/join`);
@@ -849,6 +1133,9 @@ export default defineComponent({
// Force reactivity update
await nextTick();
+ // Update channel stats after joining
+ await updateChannelStats();
+
// Show success message (optional)
console.log('Successfully joined channel:', channel.name);
}
@@ -867,20 +1154,35 @@ export default defineComponent({
await loadPublicChannels();
currentChannel.value = null;
messages.value = [];
+
+ // Update channel stats after leaving
+ if (currentChannel.value) {
+ await updateChannelStats();
+ }
}
} catch (error) {
console.error('Error leaving channel:', error);
}
};
- const loadChannelMessages = async (channelId, isAutoRefresh = false) => {
+ const loadChannelMessages = async (channelId, isAutoRefresh = false, isLoadMore = false) => {
try {
- if (!isAutoRefresh) {
+ if (!isAutoRefresh && !isLoadMore) {
loading.value = true;
}
- const response = await axios.get(`/api/chat/channels/${channelId}/messages`);
+ if (isLoadMore) {
+ pagination.value.loadingMore = true;
+ }
+
+ const params = new URLSearchParams({
+ limit: pagination.value.limit.toString(),
+ offset: pagination.value.offset.toString()
+ });
+
+ const response = await axios.get(`/api/chat/channels/${channelId}/messages?${params}`);
if (response.data.success) {
const newMessages = response.data.data.reverse(); // Show newest first
+ const paginationInfo = response.data.pagination;
// For auto-refresh, only update if there are new messages
if (isAutoRefresh) {
@@ -893,8 +1195,36 @@ export default defineComponent({
await nextTick();
scrollToBottom();
}
+ } else if (isLoadMore) {
+ // For load more, prepend older messages to the beginning
+ if (newMessages.length > 0) {
+ const scrollPosition = messagesContainer.value?.scrollTop || 0;
+ const scrollHeight = messagesContainer.value?.scrollHeight || 0;
+
+ messages.value = [...newMessages, ...messages.value];
+
+ // Update pagination from server response
+ pagination.value.offset = paginationInfo.offset + pagination.value.limit;
+ pagination.value.hasMore = paginationInfo.hasMore;
+ pagination.value.totalMessages = paginationInfo.total;
+
+ // Maintain scroll position after adding older messages
+ await nextTick();
+ if (messagesContainer.value) {
+ const newScrollHeight = messagesContainer.value.scrollHeight;
+ const scrollDiff = newScrollHeight - scrollHeight;
+ messagesContainer.value.scrollTop = scrollPosition + scrollDiff;
+ }
+ } else {
+ pagination.value.hasMore = false;
+ }
} else {
+ // Initial load
messages.value = newMessages;
+ pagination.value.offset = paginationInfo.offset + pagination.value.limit;
+ pagination.value.hasMore = paginationInfo.hasMore;
+ pagination.value.totalMessages = paginationInfo.total;
+
await nextTick();
scrollToBottom();
}
@@ -902,9 +1232,33 @@ export default defineComponent({
} catch (error) {
console.error('Error loading messages:', error);
} finally {
- if (!isAutoRefresh) {
+ if (!isAutoRefresh && !isLoadMore) {
loading.value = false;
}
+ if (isLoadMore) {
+ pagination.value.loadingMore = false;
+ }
+ }
+ };
+
+ const loadMoreMessages = async () => {
+ if (!currentChannel.value || pagination.value.loadingMore || !pagination.value.hasMore) {
+ return;
+ }
+
+ await loadChannelMessages(currentChannel.value.channelId, false, true);
+ };
+
+ const handleScroll = () => {
+ if (!messagesContainer.value || pagination.value.loadingMore || !pagination.value.hasMore) {
+ return;
+ }
+
+ const { scrollTop } = messagesContainer.value;
+
+ // Load more messages when user scrolls to the top (or near the top)
+ if (scrollTop <= 100) {
+ loadMoreMessages();
}
};
@@ -921,15 +1275,26 @@ export default defineComponent({
loading.value = true;
try {
- const response = await axios.post(`/api/chat/channels/${currentChannel.value.channelId}/messages`, {
+ const messageData = {
content: messageText.value,
messageType: 'text'
- });
+ };
+
+ // Add quoted message ID if there's a quoted message
+ if (quotedMessage.value) {
+ messageData.quotedMessageId = quotedMessage.value.id;
+ }
+
+ const response = await axios.post(`/api/chat/channels/${currentChannel.value.channelId}/messages`, messageData);
if (response.data.success) {
const newMessage = response.data.data;
messages.value.push(newMessage);
messageText.value = '';
+ quotedMessage.value = null; // Clear quoted message after sending
+
+ // Update channel stats after sending message
+ await updateChannelStats();
await nextTick();
scrollToBottom();
@@ -945,6 +1310,158 @@ export default defineComponent({
}
};
+ const quoteMessage = (message) => {
+ quotedMessage.value = message;
+ // Focus on the input field
+ nextTick(() => {
+ const input = document.querySelector('.message-input input');
+ if (input) {
+ input.focus();
+ }
+ });
+ };
+
+ const replyToMessage = (message) => {
+ quotedMessage.value = message;
+ // Focus on the input field
+ nextTick(() => {
+ const input = document.querySelector('.message-input input');
+ if (input) {
+ input.focus();
+ }
+ });
+ };
+
+ const cancelQuote = () => {
+ quotedMessage.value = null;
+ };
+
+ const isLiked = (message) => {
+ if (!message.reactions || !message.reactions['❤️']) return false;
+ return message.reactions['❤️'].includes(currentUser.value.id);
+ };
+
+ const isDisliked = (message) => {
+ if (!message.reactions || !message.reactions['👎']) return false;
+ return message.reactions['👎'].includes(currentUser.value.id);
+ };
+
+ const toggleLike = async (message) => {
+ const loadingKey = `like-${message.id}`;
+
+ // Prevent multiple clicks
+ if (reactingMessages.value.has(loadingKey)) return;
+
+ try {
+ reactingMessages.value.add(loadingKey);
+ const wasLiked = isLiked(message);
+
+ // Store original state for rollback
+ const originalReactions = JSON.parse(JSON.stringify(message.reactions || {}));
+
+ if (wasLiked) {
+ // Remove like
+ await axios.delete(`/api/chat/messages/${message.id}/reactions`, {
+ data: { emoji: '❤️' }
+ });
+
+ // Update local state immediately
+ if (message.reactions && message.reactions['❤️']) {
+ message.reactions['❤️'] = message.reactions['❤️'].filter(id => id !== currentUser.value.id);
+ if (message.reactions['❤️'].length === 0) {
+ delete message.reactions['❤️'];
+ }
+ }
+ } else {
+ // Add like
+ await axios.post(`/api/chat/messages/${message.id}/reactions`, {
+ emoji: '❤️'
+ });
+
+ // Update local state immediately
+ if (!message.reactions) {
+ message.reactions = {};
+ }
+ if (!message.reactions['❤️']) {
+ message.reactions['❤️'] = [];
+ }
+ if (!message.reactions['❤️'].includes(currentUser.value.id)) {
+ message.reactions['❤️'].push(currentUser.value.id);
+ }
+ }
+
+ // Force reactivity update
+ messages.value = [...messages.value];
+ } catch (error) {
+ console.error('Error toggling like:', error);
+ // Revert local changes on error
+ message.reactions = originalReactions;
+ messages.value = [...messages.value];
+ // Optionally show error message to user
+ // You can add a toast notification here
+ } finally {
+ reactingMessages.value.delete(loadingKey);
+ }
+ };
+
+ const toggleDislike = async (message) => {
+ const loadingKey = `dislike-${message.id}`;
+
+ // Prevent multiple clicks
+ if (reactingMessages.value.has(loadingKey)) return;
+
+ try {
+ reactingMessages.value.add(loadingKey);
+ const wasDisliked = isDisliked(message);
+
+ // Store original state for rollback
+ const originalReactions = JSON.parse(JSON.stringify(message.reactions || {}));
+
+ if (wasDisliked) {
+ // Remove dislike
+ await axios.delete(`/api/chat/messages/${message.id}/reactions`, {
+ data: { emoji: '👎' }
+ });
+
+ // Update local state immediately
+ if (message.reactions && message.reactions['👎']) {
+ message.reactions['👎'] = message.reactions['👎'].filter(id => id !== currentUser.value.id);
+ if (message.reactions['👎'].length === 0) {
+ delete message.reactions['👎'];
+ }
+ }
+ } else {
+ // Add dislike
+ await axios.post(`/api/chat/messages/${message.id}/reactions`, {
+ emoji: '👎'
+ });
+
+ // Update local state immediately
+ if (!message.reactions) {
+ message.reactions = {};
+ }
+ if (!message.reactions['👎']) {
+ message.reactions['👎'] = [];
+ }
+ if (!message.reactions['👎'].includes(currentUser.value.id)) {
+ message.reactions['👎'].push(currentUser.value.id);
+ }
+ }
+
+ // Force reactivity update
+ messages.value = [...messages.value];
+ } catch (error) {
+ console.error('Error toggling dislike:', error);
+ // Revert local changes on error
+ message.reactions = originalReactions;
+ messages.value = [...messages.value];
+ // Optionally show error message to user
+ // You can add a toast notification here
+ } finally {
+ reactingMessages.value.delete(loadingKey);
+ }
+ };
+
const createChannel = async () => {
if (!newChannel.value.name.trim()) return;
@@ -982,7 +1499,31 @@ export default defineComponent({
}
};
-
+ const updateChannelStats = async () => {
+ if (!currentChannel.value) return;
+
+ try {
+ const response = await axios.get(`/api/chat/channels/${currentChannel.value.channelId}/stats`);
+ if (response.data.success) {
+ const stats = response.data.data;
+
+ // Update current channel stats
+ if (currentChannel.value) {
+ currentChannel.value.messageCount = stats.messageCount;
+ currentChannel.value.memberCount = stats.memberCount;
+ }
+
+ // Update channel in userChannels list
+ const channelIndex = userChannels.value.findIndex(c => c.id === currentChannel.value.id);
+ if (channelIndex !== -1) {
+ userChannels.value[channelIndex].messageCount = stats.messageCount;
+ userChannels.value[channelIndex].memberCount = stats.memberCount;
+ }
+ }
+ } catch (error) {
+ console.error('Error updating channel stats:', error);
+ }
+ };
const refreshChannels = async () => {
await loadUserChannels();
@@ -1131,6 +1672,33 @@ export default defineComponent({
}).format(date);
};
+ // Function to mask email for security
+ const maskEmail = (email) => {
+ if (!email) return '';
+
+ const [username, domain] = email.split('@');
+ if (!domain) return email;
+
+ // Mask username (show first and last character)
+ let maskedUsername = username;
+ if (username.length > 2) {
+ maskedUsername = username.charAt(0) + '*'.repeat(username.length - 2) + username.charAt(username.length - 1);
+ } else if (username.length === 2) {
+ maskedUsername = username.charAt(0) + '*';
+ }
+
+ // Mask domain (show first character of each part)
+ const domainParts = domain.split('.');
+ const maskedDomainParts = domainParts.map(part => {
+ if (part.length > 1) {
+ return part.charAt(0) + '*'.repeat(part.length - 1);
+ }
+ return part;
+ });
+
+ return `${maskedUsername}@${maskedDomainParts.join('.')}`;
+ };
+
// Initialize
onMounted(() => {
loadUserChannels();
@@ -1171,6 +1739,7 @@ export default defineComponent({
memberSearch,
searchResults,
searchingUsers,
+ pagination,
sendMessage,
createChannel,
selectChannel,
@@ -1184,8 +1753,22 @@ export default defineComponent({
addMemberToChannel,
removeMemberFromChannel,
updateUserChannels,
+ updateChannelStats,
formatTime,
formatDate,
+ quotedMessage,
+ showMessageActions,
+ quoteMessage,
+ replyToMessage,
+ cancelQuote,
+ isLiked,
+ isDisliked,
+ toggleLike,
+ toggleDislike,
+ reactingMessages,
+ handleScroll,
+ loadMoreMessages,
+ maskEmail,
};
},
});
@@ -1526,6 +2109,23 @@ export default defineComponent({
background-color: rgba(255, 255, 255, 0.1);
}
+/* Load more indicator */
+.load-more-indicator {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 16px;
+ background-color: rgba(0, 0, 0, 0.02);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+ color: rgba(0, 0, 0, 0.7);
+}
+
+.v-theme--dark .load-more-indicator {
+ background-color: rgba(255, 255, 255, 0.02);
+ border-bottom-color: rgba(255, 255, 255, 0.08);
+ color: rgba(255, 255, 255, 0.7);
+}
+
/* Mobile optimizations */
@media (max-width: 768px) {
.chat-container {
@@ -1648,6 +2248,551 @@ export default defineComponent({
box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1);
}
+/* New styles for quoted message preview */
+.quoted-message-preview {
+ background-color: rgba(0, 0, 0, 0.05);
+ border-left: 3px solid #1976d2;
+ padding: 12px;
+ margin-bottom: 12px;
+ border-radius: 8px;
+ position: relative;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
-
+.quoted-preview-content {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+.quoted-preview-header {
+ display: flex;
+ align-items: center;
+ background-color: rgba(25, 118, 210, 0.1);
+ padding: 4px 8px;
+ border-radius: 6px;
+ font-size: 0.75rem;
+ color: #1976d2;
+}
+
+.quoted-preview-text {
+ font-size: 0.875rem;
+ color: rgba(0, 0, 0, 0.7);
+ word-break: break-word;
+ line-height: 1.4;
+}
+
+.v-theme--dark .quoted-message-preview {
+ background-color: rgba(255, 255, 255, 0.05);
+ border-left-color: #1976d2;
+}
+
+.v-theme--dark .quoted-preview-header {
+ background-color: rgba(25, 118, 210, 0.2);
+ color: #64b5f6;
+}
+
+.v-theme--dark .quoted-preview-text {
+ color: rgba(255, 255, 255, 0.7);
+}
+
+/* Message actions styles */
+.message-actions {
+ display: flex;
+ gap: 4px;
+ margin-top: 8px;
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ justify-content: flex-start;
+}
+
+.message-item:hover .message-actions {
+ opacity: 1;
+}
+
+.message-own .message-actions {
+ justify-content: flex-end;
+}
+
+.action-btn {
+ color: rgba(0, 0, 0, 0.6) !important;
+ transition: all 0.2s ease;
+}
+
+.action-btn:hover {
+ color: #1976d2 !important;
+ background-color: rgba(25, 118, 210, 0.1) !important;
+}
+
+.action-btn.liked {
+ color: #f44336 !important;
+}
+
+.action-btn.liked:hover {
+ color: #d32f2f !important;
+ background-color: rgba(244, 67, 54, 0.1) !important;
+}
+
+.action-btn.disliked {
+ color: #ff9800 !important;
+}
+
+.action-btn.disliked:hover {
+ color: #f57c00 !important;
+ background-color: rgba(255, 152, 0, 0.1) !important;
+}
+
+.v-theme--dark .action-btn {
+ color: rgba(255, 255, 255, 0.6) !important;
+}
+
+.v-theme--dark .action-btn:hover {
+ color: #64b5f6 !important;
+ background-color: rgba(100, 181, 246, 0.1) !important;
+}
+
+.v-theme--dark .action-btn.liked {
+ color: #ef5350 !important;
+}
+
+.v-theme--dark .action-btn.liked:hover {
+ color: #e53935 !important;
+ background-color: rgba(239, 83, 80, 0.1) !important;
+}
+
+.v-theme--dark .action-btn.disliked {
+ color: #ffb74d !important;
+}
+
+.v-theme--dark .action-btn.disliked:hover {
+ color: #ffa726 !important;
+ background-color: rgba(255, 183, 77, 0.1) !important;
+}
+
+/* Improve quoted message styling */
+.quoted-message {
+ background-color: rgba(0, 0, 0, 0.05);
+ border-left: 3px solid #1976d2;
+ padding: 12px;
+ margin-bottom: 12px;
+ border-radius: 8px;
+ position: relative;
+}
+
+.quoted-content {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.quoted-sender {
+ font-weight: 600;
+ font-size: 0.75rem;
+ color: #1976d2;
+}
+
+.quoted-text {
+ font-size: 0.875rem;
+ color: rgba(0, 0, 0, 0.7);
+ line-height: 1.4;
+ word-break: break-word;
+}
+
+.v-theme--dark .quoted-message {
+ background-color: rgba(255, 255, 255, 0.05);
+ border-left-color: #1976d2;
+}
+
+.v-theme--dark .quoted-sender {
+ color: #64b5f6;
+}
+
+.v-theme--dark .quoted-text {
+ color: rgba(255, 255, 255, 0.7);
+}
+
+/* Reaction chip styles */
+.reaction-chip {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 8px;
+ border-radius: 12px;
+ font-weight: 600;
+ font-size: 0.75rem;
+ color: rgba(0, 0, 0, 0.8);
+ background-color: rgba(0, 0, 0, 0.05);
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ transition: all 0.2s ease;
+}
+
+.reaction-chip:hover {
+ background-color: rgba(0, 0, 0, 0.08);
+ border-color: rgba(0, 0, 0, 0.2);
+}
+
+.reaction-chip.like-chip {
+ color: #f44336; /* Red for like */
+ background-color: rgba(244, 67, 54, 0.1);
+ border-color: rgba(244, 67, 54, 0.2);
+}
+
+.reaction-chip.like-chip:hover {
+ background-color: rgba(244, 67, 54, 0.2);
+ border-color: rgba(244, 67, 54, 0.3);
+}
+
+.reaction-chip.dislike-chip {
+ color: #ff9800; /* Orange for dislike */
+ background-color: rgba(255, 152, 0, 0.1);
+ border-color: rgba(255, 152, 0, 0.2);
+}
+
+.reaction-chip.dislike-chip:hover {
+ background-color: rgba(255, 152, 0, 0.2);
+ border-color: rgba(255, 152, 0, 0.3);
+}
+
+.v-theme--dark .reaction-chip {
+ color: rgba(255, 255, 255, 0.8);
+ background-color: rgba(255, 255, 255, 0.05);
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
+.v-theme--dark .reaction-chip:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+ border-color: rgba(255, 255, 255, 0.2);
+}
+
+.v-theme--dark .reaction-chip.like-chip {
+ color: #ef5350; /* Darker red for dark mode */
+ background-color: rgba(239, 83, 80, 0.1);
+ border-color: rgba(239, 83, 80, 0.2);
+}
+
+.v-theme--dark .reaction-chip.like-chip:hover {
+ background-color: rgba(239, 83, 80, 0.2);
+ border-color: rgba(239, 83, 80, 0.3);
+}
+
+.v-theme--dark .reaction-chip.dislike-chip {
+ color: #ffa726; /* Darker orange for dark mode */
+ background-color: rgba(255, 167, 38, 0.1);
+ border-color: rgba(255, 167, 38, 0.2);
+}
+
+.v-theme--dark .reaction-chip.dislike-chip:hover {
+ background-color: rgba(255, 167, 38, 0.2);
+ border-color: rgba(255, 167, 38, 0.3);
+}
+
+/* Dialog Styles */
+.create-channel-dialog,
+.member-dialog,
+.add-member-dialog {
+ border-radius: 16px;
+ overflow: hidden;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
+}
+
+.dialog-header {
+ background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
+ color: white;
+ padding: 24px;
+ position: relative;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.dialog-header::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(5px);
+}
+
+.dialog-header > div {
+ position: relative;
+ z-index: 1;
+}
+
+.dialog-header h2 {
+ color: white !important;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
+ font-weight: 600;
+}
+
+.dialog-header p {
+ color: rgba(255, 255, 255, 0.9) !important;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+}
+
+/* Dark theme support for dialog headers */
+.v-theme--dark .dialog-header {
+ background: linear-gradient(135deg, #1565c0 0%, #0d47a1 100%);
+}
+
+.v-theme--dark .dialog-header h2 {
+ color: white !important;
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
+}
+
+.v-theme--dark .dialog-header p {
+ color: rgba(255, 255, 255, 0.95) !important;
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+}
+
+.dialog-content {
+ padding: 24px;
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+.dialog-actions {
+ padding: 16px 24px;
+ background-color: rgba(0, 0, 0, 0.02);
+ border-top: 1px solid rgba(0, 0, 0, 0.08);
+}
+
+.add-member-btn {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ transition: all 0.3s ease;
+}
+
+.add-member-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
+}
+
+/* Member Stats Cards */
+.member-stats {
+ margin-bottom: 24px;
+}
+
+.stat-card {
+ border-radius: 12px;
+ transition: all 0.3s ease;
+ cursor: pointer;
+}
+
+.stat-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
+}
+
+.stat-card .v-card-text {
+ padding: 20px;
+}
+
+/* Member List */
+.member-list {
+ background: transparent;
+}
+
+.member-item {
+ margin-bottom: 8px;
+ border-radius: 12px;
+ transition: all 0.3s ease;
+ border: 1px solid transparent;
+}
+
+.member-item:hover {
+ background-color: rgba(0, 0, 0, 0.04);
+ border-color: rgba(0, 0, 0, 0.08);
+ transform: translateX(4px);
+}
+
+.remove-btn {
+ opacity: 0;
+ transition: all 0.3s ease;
+}
+
+.member-item:hover .remove-btn {
+ opacity: 1;
+}
+
+/* Search Results */
+.search-results {
+ margin-top: 16px;
+}
+
+.search-result-list {
+ background: transparent;
+}
+
+.search-result-item {
+ margin-bottom: 8px;
+ border-radius: 12px;
+ transition: all 0.3s ease;
+ border: 1px solid transparent;
+ cursor: pointer;
+}
+
+.search-result-item:hover {
+ background-color: rgba(0, 0, 0, 0.04);
+ border-color: rgba(0, 0, 0, 0.08);
+ transform: translateX(4px);
+}
+
+/* Loading and Empty States */
+.loading-members,
+.searching-users,
+.no-results,
+.search-placeholder {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 40px 20px;
+ text-align: center;
+}
+
+.loading-members p,
+.searching-users p {
+ color: rgba(0, 0, 0, 0.7);
+ margin-top: 16px;
+}
+
+.no-results h4,
+.search-placeholder h4 {
+ color: rgba(0, 0, 0, 0.6);
+ margin-bottom: 8px;
+}
+
+.no-results p,
+.search-placeholder p {
+ color: rgba(0, 0, 0, 0.5);
+ max-width: 300px;
+}
+
+/* Dark Theme Support for Dialogs */
+.v-theme--dark .dialog-actions {
+ background-color: rgba(255, 255, 255, 0.02);
+ border-top-color: rgba(255, 255, 255, 0.08);
+}
+
+.v-theme--dark .stat-card:hover {
+ box-shadow: 0 8px 25px rgba(255, 255, 255, 0.1);
+}
+
+.v-theme--dark .member-item:hover {
+ background-color: rgba(255, 255, 255, 0.04);
+ border-color: rgba(255, 255, 255, 0.08);
+}
+
+.v-theme--dark .search-result-item:hover {
+ background-color: rgba(255, 255, 255, 0.04);
+ border-color: rgba(255, 255, 255, 0.08);
+}
+
+.v-theme--dark .loading-members p,
+.v-theme--dark .searching-users p {
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.v-theme--dark .no-results h4,
+.v-theme--dark .search-placeholder h4 {
+ color: rgba(255, 255, 255, 0.6);
+}
+
+.v-theme--dark .no-results p,
+.v-theme--dark .search-placeholder p {
+ color: rgba(255, 255, 255, 0.5);
+}
+
+/* Mobile Responsive for Dialogs */
+@media (max-width: 768px) {
+ .create-channel-dialog,
+ .member-dialog,
+ .add-member-dialog {
+ margin: 16px;
+ border-radius: 12px;
+ }
+
+ .dialog-header {
+ padding: 20px;
+ }
+
+ .dialog-content {
+ padding: 20px;
+ max-height: 50vh;
+ }
+
+ .dialog-actions {
+ padding: 12px 20px;
+ }
+
+ .member-stats .v-row {
+ margin: 0;
+ }
+
+ .member-stats .v-col {
+ padding: 8px;
+ }
+
+ .stat-card .v-card-text {
+ padding: 16px;
+ }
+
+ .add-member-btn {
+ font-size: 0.875rem;
+ padding: 8px 16px;
+ }
+}
+
+/* Animation for dialog entrance */
+.v-dialog .v-card {
+ animation: dialogSlideIn 0.3s ease-out;
+}
+
+@keyframes dialogSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(-20px) scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+/* Hover effects for interactive elements */
+.v-btn:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.v-chip:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* Custom scrollbar for dialog content */
+.dialog-content::-webkit-scrollbar {
+ width: 6px;
+}
+
+.dialog-content::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.dialog-content::-webkit-scrollbar-thumb {
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 3px;
+}
+
+.dialog-content::-webkit-scrollbar-thumb:hover {
+ background: rgba(0, 0, 0, 0.3);
+}
+
+.v-theme--dark .dialog-content::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.2);
+}
+
+.v-theme--dark .dialog-content::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.3);
+}
+
\ No newline at end of file