feat(users): implement bulk actions logic

This commit is contained in:
CycroftX
2026-02-09 21:40:09 +05:30
parent 9ca94fa06f
commit df4d881437
2 changed files with 161 additions and 36 deletions

View File

@@ -1,5 +1,5 @@
// BulkActionsDropdown - Bulk operations menu for selected users // BulkActionsDropdown - Bulk operations menu for selected users
import { useState } from 'react'; import { useState, useTransition } from 'react';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -37,6 +37,13 @@ import { mockTags } from '../data/mockUserCrmData';
import type { User } from '@/lib/types/user'; import type { User } from '@/lib/types/user';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import {
bulkSuspendUsers,
bulkBanUsers,
bulkDeleteUsers,
bulkVerifyUsers,
bulkTagUsers,
} from '@/lib/actions/user-management';
interface BulkActionsDropdownProps { interface BulkActionsDropdownProps {
selectedUsers: User[]; selectedUsers: User[];
@@ -55,54 +62,73 @@ export function BulkActionsDropdown({
}: BulkActionsDropdownProps) { }: BulkActionsDropdownProps) {
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
const [actionType, setActionType] = useState<'suspend' | 'ban' | 'delete' | 'export' | 'verify'>('suspend'); const [actionType, setActionType] = useState<'suspend' | 'ban' | 'delete' | 'export' | 'verify'>('suspend');
const [isProcessing, setIsProcessing] = useState(false); const [isPending, startTransition] = useTransition();
const count = selectedUsers.length; const count = selectedUsers.length;
const userIds = selectedUsers.map(u => u.id);
const handleAction = (action: 'suspend' | 'ban' | 'delete' | 'export' | 'verify') => { const handleAction = (action: 'suspend' | 'ban' | 'delete' | 'export' | 'verify') => {
setActionType(action); setActionType(action);
setConfirmDialogOpen(true); setConfirmDialogOpen(true);
}; };
const handleConfirm = async () => { const executeServerAction = async (action: () => Promise<{ success: boolean; message: string }>) => {
setIsProcessing(true); startTransition(async () => {
try { try {
// Simulate API call const result = await action();
await new Promise((resolve) => setTimeout(resolve, 1500)); if (result.success) {
toast.success(result.message);
switch (actionType) { setConfirmDialogOpen(false);
case 'suspend': onClearSelection();
toast.success(`Suspended ${count} user${count > 1 ? 's' : ''}`); } else {
break; toast.error(result.message);
case 'ban': }
toast.success(`Banned ${count} user${count > 1 ? 's' : ''}`); } catch (error) {
break; toast.error("An unexpected error occurred.");
case 'delete':
toast.success(`Deleted ${count} user${count > 1 ? 's' : ''}`);
break;
case 'export':
toast.success(`Exported ${count} user${count > 1 ? 's' : ''} to CSV`);
break;
case 'verify':
toast.success(`Verified ${count} user${count > 1 ? 's' : ''}`);
break;
} }
});
};
setConfirmDialogOpen(false); const handleConfirm = async () => {
onClearSelection(); switch (actionType) {
} catch (error) { case 'suspend':
toast.error('Operation failed'); executeServerAction(() => bulkSuspendUsers(userIds));
} finally { break;
setIsProcessing(false); case 'ban':
executeServerAction(() => bulkBanUsers(userIds));
break;
case 'delete':
executeServerAction(() => bulkDeleteUsers(userIds));
break;
case 'verify':
executeServerAction(() => bulkVerifyUsers(userIds));
break;
case 'export':
// Export is usually client-side or separate download endpoint,
// but we can simulate a server request or just handle it here.
toast.success(`Exporting ${count} users...`);
setTimeout(() => {
toast.success("Export Complete (Mock CSV Download)");
setConfirmDialogOpen(false);
onClearSelection();
}, 1000);
break;
} }
}; };
const handleTagUsers = async (tagId: string) => { const handleTagUsers = (tagId: string) => {
const tag = mockTags.find((t) => t.id === tagId); const tag = mockTags.find((t) => t.id === tagId);
if (!tag) return; if (!tag) return;
toast.success(`Added tag "${tag.name}" to ${count} user${count > 1 ? 's' : ''}`); startTransition(async () => {
onClearSelection(); const result = await bulkTagUsers(userIds, tagId);
if (result.success) {
toast.success(result.message, { description: `Applied tag: ${tag.name}` });
onClearSelection();
} else {
toast.error(result.message);
}
});
}; };
const actionConfig = { const actionConfig = {
@@ -246,16 +272,16 @@ export function BulkActionsDropdown({
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => setConfirmDialogOpen(false)} disabled={isProcessing}> <Button variant="outline" onClick={() => setConfirmDialogOpen(false)} disabled={isPending}>
Cancel Cancel
</Button> </Button>
<Button <Button
variant="destructive" variant="destructive"
className={config.buttonClass} className={config.buttonClass}
onClick={handleConfirm} onClick={handleConfirm}
disabled={isProcessing} disabled={isPending}
> >
{isProcessing ? ( {isPending ? (
<> <>
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> Processing... <Loader2 className="h-4 w-4 mr-2 animate-spin" /> Processing...
</> </>

View File

@@ -144,3 +144,102 @@ export async function createSupportTicket(userId: string, summary: string) {
return { success: false, message: "Failed to create ticket." }; return { success: false, message: "Failed to create ticket." };
} }
} }
// --- Bulk Actions ---
export async function bulkSuspendUsers(userIds: string[]) {
try {
const admin = await verifyAdmin();
// Simulate DB update
await new Promise(resolve => setTimeout(resolve, 1500));
// Log for each user or a single bulk log
await logAdminAction({
actorId: admin.id,
action: 'BULK_SUSPEND',
targetId: 'multiple',
details: { count: userIds.length, userIds },
});
return { success: true, message: `Successfully suspended ${userIds.length} users.` };
} catch (error) {
return { success: false, message: "Failed to perform bulk suspension." };
}
}
export async function bulkBanUsers(userIds: string[]) {
try {
const admin = await verifyAdmin();
await new Promise(resolve => setTimeout(resolve, 1500));
await logAdminAction({
actorId: admin.id,
action: 'BULK_BAN',
targetId: 'multiple',
details: { count: userIds.length, userIds },
});
return { success: true, message: `Successfully banned ${userIds.length} users.` };
} catch (error) {
return { success: false, message: "Failed to perform bulk ban." };
}
}
export async function bulkDeleteUsers(userIds: string[]) {
try {
const admin = await verifyAdmin();
await new Promise(resolve => setTimeout(resolve, 2000));
await logAdminAction({
actorId: admin.id,
action: 'BULK_DELETE',
targetId: 'multiple',
details: { count: userIds.length, userIds },
});
return { success: true, message: `Successfully deleted ${userIds.length} users.` };
} catch (error) {
return { success: false, message: "Failed to perform bulk delete." };
}
}
export async function bulkVerifyUsers(userIds: string[]) {
try {
const admin = await verifyAdmin();
await new Promise(resolve => setTimeout(resolve, 1000));
await logAdminAction({
actorId: admin.id,
action: 'BULK_VERIFY',
targetId: 'multiple',
details: { count: userIds.length, userIds },
});
return { success: true, message: `Successfully verified ${userIds.length} users.` };
} catch (error) {
return { success: false, message: "Failed to verify users." };
}
}
export async function bulkTagUsers(userIds: string[], tagId: string) {
try {
const admin = await verifyAdmin();
await new Promise(resolve => setTimeout(resolve, 1000));
await logAdminAction({
actorId: admin.id,
action: 'BULK_TAG_ADD',
targetId: 'multiple',
details: { count: userIds.length, userIds, tagId },
});
return { success: true, message: `Added tag to ${userIds.length} users.` };
} catch (error) {
return { success: false, message: "Failed to tag users." };
}
}