Skip to content
Open
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
72 changes: 46 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,34 @@ 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
const { data, error } = await supabase
.from('profiles')
.select('id, full_name, avatar_url');

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,
Comment on lines +57 to +61
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.

Profiles are mapped with email: '' for privacy, but the component later searches and renders user.email as if it’s meaningful. This makes email search ineffective and can yield blank UI output. Consider either (1) removing email-based filtering/rendering when email is empty, or (2) adding a non-sensitive, non-empty identifier (e.g., username) to the fetched profile fields and using that instead.

Copilot uses AI. Check for mistakes.
avatar_url: profile.avatar_url,
},
}));

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

Expand All @@ -60,10 +79,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 +102,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 +120,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 +138,10 @@ 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 ||
user.id;
};

return (
Expand Down Expand Up @@ -196,7 +216,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 +229,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 +299,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 +325,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