import React, { useState, useEffect } from 'react'; import './BulkActionPermissionModal.scss'; import { translateString } from '../utils/helpers/'; interface User { name: string; username: string; } interface BulkActionPermissionModalProps { isOpen: boolean; permissionType: 'viewer' | 'editor' | 'owner' | null; selectedMediaIds: string[]; onCancel: () => void; onSuccess: (message: string) => void; onError: (message: string) => void; csrfToken: string; } export const BulkActionPermissionModal: React.FC = ({ isOpen, permissionType, selectedMediaIds, onCancel, onSuccess, onError, csrfToken, }) => { const [existingUsers, setExistingUsers] = useState([]); const [existingSearchTerm, setExistingSearchTerm] = useState(''); const [usersToAdd, setUsersToAdd] = useState([]); const [usersToRemove, setUsersToRemove] = useState([]); const [searchResults, setSearchResults] = useState([]); const [addSearchTerm, setAddSearchTerm] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const [searchTimeout, setSearchTimeout] = useState(null); useEffect(() => { if (isOpen && permissionType && selectedMediaIds.length > 0) { fetchExistingUsers(); } else { // Reset state when modal closes setExistingUsers([]); setExistingSearchTerm(''); setUsersToAdd([]); setUsersToRemove([]); setSearchResults([]); setAddSearchTerm(''); } }, [isOpen, permissionType, selectedMediaIds]); const fetchExistingUsers = async () => { setIsLoading(true); try { const response = await fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken, }, body: JSON.stringify({ action: 'get_ownership', media_ids: selectedMediaIds, ownership_type: permissionType, }), }); if (!response.ok) { throw new Error(translateString('Failed to fetch existing users')); } const data = await response.json(); setExistingUsers(data.results || []); } catch (error) { console.error('Error fetching existing users:', error); onError(translateString('Failed to load existing permissions')); } finally { setIsLoading(false); } }; const searchUsers = async (name: string) => { if (!name.trim()) { setSearchResults([]); return; } try { const response = await fetch(`/api/v1/users?name=${encodeURIComponent(name)}&exclude_self=True`); if (!response.ok) { throw new Error(translateString('Failed to search users')); } const data = await response.json(); // API returns paginated response with results array const users = data.results || []; setSearchResults(Array.isArray(users) ? users : []); } catch (error) { console.error('Error searching users:', error); setSearchResults([]); } }; const handleAddSearchChange = (value: string) => { setAddSearchTerm(value); // Clear previous timeout if (searchTimeout) { clearTimeout(searchTimeout); } // Only search if 3 or more characters if (value.trim().length < 3) { setSearchResults([]); return; } // Set new timeout for debounced search const timeout = setTimeout(() => { searchUsers(value); }, 300); setSearchTimeout(timeout); }; const addUserToList = (username: string, name: string) => { const userDisplay = `${name} - ${username}`; if (!usersToAdd.includes(userDisplay) && !existingUsers.includes(userDisplay)) { setUsersToAdd([...usersToAdd, userDisplay]); setAddSearchTerm(''); setSearchResults([]); } }; const removeUserFromAddList = (user: string) => { setUsersToAdd(usersToAdd.filter((u) => u !== user)); }; const markUserForRemoval = (user: string) => { if (!usersToRemove.includes(user)) { setUsersToRemove([...usersToRemove, user]); } }; const unmarkUserForRemoval = (user: string) => { setUsersToRemove(usersToRemove.filter((u) => u !== user)); }; const extractUsername = (userDisplay: string): string => { // Extract username from "Name - username" format const parts = userDisplay.split(' - '); return parts.length > 1 ? parts[parts.length - 1] : userDisplay; }; const handleProceed = async () => { setIsProcessing(true); try { // First, add users if any if (usersToAdd.length > 0) { const usernamesToAdd = usersToAdd.map(extractUsername); const addResponse = await fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken, }, body: JSON.stringify({ action: 'set_ownership', media_ids: selectedMediaIds, ownership_type: permissionType, users: usernamesToAdd, }), }); if (!addResponse.ok) { throw new Error(translateString('Failed to add users')); } } // Then, remove users if any if (usersToRemove.length > 0) { const usernamesToRemove = usersToRemove.map(extractUsername); const removeResponse = await fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken, }, body: JSON.stringify({ action: 'remove_ownership', media_ids: selectedMediaIds, ownership_type: permissionType, users: usernamesToRemove, }), }); if (!removeResponse.ok) { throw new Error(translateString('Failed to remove users')); } } const permissionLabel = permissionType === 'viewer' ? translateString('Co-Viewers') : permissionType === 'editor' ? translateString('Co-Editors') : translateString('Co-Owners'); onSuccess(`${translateString('Successfully updated')} ${permissionLabel}`); onCancel(); } catch (error) { console.error('Error processing permissions:', error); onError(translateString('Failed to update permissions. Please try again.')); } finally { setIsProcessing(false); } }; const filteredExistingUsers = existingUsers.filter((user) => user.toLowerCase().includes(existingSearchTerm.toLowerCase()) ); if (!isOpen) return null; const permissionLabel = permissionType === 'viewer' ? translateString('Co-Viewers') : permissionType === 'editor' ? translateString('Co-Editors') : translateString('Co-Owners'); return (

{translateString('Manage')} {permissionLabel}

{translateString('Users')}

handleAddSearchChange(e.target.value)} />
{searchResults.length > 0 && (
{searchResults.slice(0, 10).map((user) => (
addUserToList(user.username, user.name)} > {user.name} - {user.username}
))}
)}
{usersToAdd.length === 0 ? (
{translateString('No users to add')}
) : ( usersToAdd.map((user) => (
{user}
)) )}

{translateString('To add')} {selectedMediaIds.length > 1 && ( ? )}

setExistingSearchTerm(e.target.value)} />
{isLoading ? (
{translateString('Loading existing users...')}
) : (
{filteredExistingUsers.length === 0 ? (
{translateString('No existing')} {permissionLabel.toLowerCase()}
) : ( filteredExistingUsers.map((user) => { const isMarkedForRemoval = usersToRemove.includes(user); return (
{user}
); }) )}
)}
); };