Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 50 additions & 26 deletions src/components/CreateConversationModal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useState, useEffect } from 'react';
import { useCreateConversation } from '../hooks/useMessaging';
import { supabase } from '../supabase-client';
import { supabase, isBackendAvailable } from '../supabase-client';
import { useAuth } from '../hooks/useAuth';
import { X, Search, Users, MessageCircle, Check } from 'lucide-react';
import type { Conversation } from '../types/messaging';
import { showSuccess,showError } from '../utils/toast';
import { showSuccess, showError } from '../utils/toast';


interface CreateConversationModalProps {
Expand Down Expand Up @@ -39,15 +39,39 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve
// Fetch users for selection
useEffect(() => {
const fetchUsers = async () => {
if (!isBackendAvailable || !supabase) {
console.warn('Backend unavailable - cannot fetch users');
setUsers([]);
return;
}

try {
const { data, error } = await supabase.auth.admin.listUsers();
// βœ… SECURE: Query profiles table with RLS instead of admin API
let query = supabase
.from('Profiles')
.select('id, full_name, avatar_url');

if (currentUser?.id) {
query = query.neq('id', currentUser.id);
}

const { data, error } = await query;
if (error) throw error;

// Filter out current user
const filteredUsers = data.users.filter(u => u.id !== currentUser?.id);
setUsers(filteredUsers as User[]);

// Map profiles to User format
const mappedUsers = (data || []).map(profile => ({
id: profile.id,
email: '', // Email not exposed for privacy
user_metadata: {
full_name: profile.full_name,
avatar_url: profile.avatar_url,
Comment on lines +63 to +67
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users are mapped with email: '' for privacy, but this component still uses user.email for filtering and renders it in the list. This produces a blank secondary line and makes email search ineffective. Consider making email optional and updating filter/render to use available profile fields (e.g., full_name/username) instead.

Copilot uses AI. Check for mistakes.
},
}));

setUsers(mappedUsers as User[]);
} catch (error) {
console.error('Failed to fetch users:', error);
showError('Failed to load users');
}
};

Expand All @@ -60,10 +84,10 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve
const fullName = user.user_metadata?.full_name?.toLowerCase() || '';
const userName = user.user_metadata?.user_name?.toLowerCase() || '';
const email = user.email.toLowerCase();
return fullName.includes(searchLower) ||
userName.includes(searchLower) ||
email.includes(searchLower);

return fullName.includes(searchLower) ||
userName.includes(searchLower) ||
email.includes(searchLower);
});

const handleUserSelect = (user: User) => {
Expand All @@ -83,14 +107,14 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve

const handleCreate = async () => {
if (selectedUsers.length === 0) return;

if (conversationType === 'group' && !groupName.trim()) {
showError('Please enter a group name');
return;
}

setIsLoading(true);

try {
const conversationData = {
type: conversationType,
Expand All @@ -101,12 +125,12 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve
};

const conversation = await createConversation.mutateAsync(conversationData);

showSuccess(
conversationType === 'group'
conversationType === 'group'
? "Group conversation created"
: "Conversation started"
);
: "Conversation started"
);

onConversationCreated(conversation);

Expand All @@ -119,9 +143,9 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve
};

const getUserDisplayName = (user: User) => {
return user.user_metadata?.full_name ||
user.user_metadata?.user_name ||
user.email;
return user.user_metadata?.full_name ||
user.user_metadata?.user_name ||
user.email;
};

return (
Expand Down Expand Up @@ -196,7 +220,7 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-cyan-400/50 focus:ring-1 focus:ring-cyan-400/50"
/>
</div>

<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
Description (optional)
Expand All @@ -209,7 +233,7 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve
className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-cyan-400/50 focus:ring-1 focus:ring-cyan-400/50 resize-none"
/>
</div>

<div className="flex items-center gap-2">
<input
type="checkbox"
Expand Down Expand Up @@ -279,7 +303,7 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve
<div className="max-h-48 overflow-y-auto space-y-1">
{filteredUsers.map(user => {
const isSelected = selectedUsers.some(u => u.id === user.id);

return (
<div
key={user.id}
Expand All @@ -305,21 +329,21 @@ const CreateConversationModal = ({ onClose, onConversationCreated }: CreateConve
</span>
</div>
)}

<div className="flex-1">
<p className="text-white font-medium">
{getUserDisplayName(user)}
</p>
<p className="text-sm text-gray-400">{user.email}</p>
</div>

{isSelected && (
<Check className="w-5 h-5 text-cyan-400" />
)}
</div>
);
})}

{filteredUsers.length === 0 && (
<div className="text-center py-4 text-gray-400">
No users found
Expand Down
Loading