'use client'; import React, { useState, useEffect, useCallback } from 'react'; import { OperationsRole, SafeUserSummary, SafeUserDetail, } from './types'; interface OperationsUsersProps { role: OperationsRole; } export function OperationsUsers({ role: _role }: OperationsUsersProps) { const [users, setUsers] = useState([]); const [nextCursor, setNextCursor] = useState(null); const [totalCount, setTotalCount] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); const [errorMessage, setErrorMessage] = useState(null); // Filters const [searchQuery, setSearchQuery] = useState(''); const [appliedSearch, setAppliedSearch] = useState(''); const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'suspended'>('all'); const [verifiedFilter, setVerifiedFilter] = useState<'all' | 'verified' | 'unverified'>('all'); // Selected User Detail Modal / Drawer const [selectedUserId, setSelectedUserId] = useState(null); const [userDetail, setUserDetail] = useState(null); const [isLoadingDetail, setIsLoadingDetail] = useState(false); const [detailError, setDetailError] = useState(null); // Action Confirmation Modal const [actionType, setActionType] = useState< 'suspend' | 'reinstate' | 'revoke-sessions' | 'reset-password' | null >(null); const [actionReason, setActionReason] = useState(''); const [isSubmittingAction, setIsSubmittingAction] = useState(false); const [actionError, setActionError] = useState(null); const [actionSuccessMessage, setActionSuccessMessage] = useState(null); // Fetch users list const fetchUsers = useCallback( async (cursor?: string) => { if (cursor) { setIsLoadingMore(true); } else { setIsLoading(true); setErrorMessage(null); } const params = new URLSearchParams(); if (appliedSearch.trim()) params.set('q', appliedSearch.trim()); if (statusFilter !== 'all') params.set('status_filter', statusFilter); if (verifiedFilter !== 'all') params.set('verified_filter', verifiedFilter); if (cursor) params.set('cursor', cursor); try { const response = await fetch(`/api/ops/users?${params.toString()}`, { method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store', }); if (response.status === 401) { setErrorMessage('Your Operations session has expired.'); return; } if (response.status === 403) { setErrorMessage('You do not have authority to view users.'); return; } if (!response.ok) { setErrorMessage('Failed to load users from platform.'); return; } const data = await response.json(); const items: SafeUserSummary[] = Array.isArray(data?.items) ? data.items : []; if (cursor) { setUsers((prev) => [...prev, ...items]); } else { setUsers(items); } setNextCursor(data?.next_cursor ?? null); if (typeof data?.total === 'number') { setTotalCount(data.total); } } catch { setErrorMessage('Unable to connect to Operations platform.'); } finally { setIsLoading(false); setIsLoadingMore(false); } }, [appliedSearch, statusFilter, verifiedFilter] ); useEffect(() => { fetchUsers(); }, [fetchUsers]); // Fetch individual user detail const fetchUserDetail = useCallback(async (targetId: string) => { setIsLoadingDetail(true); setDetailError(null); try { const response = await fetch(`/api/ops/users/${encodeURIComponent(targetId)}`, { method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store', }); if (response.status === 401) { setDetailError('Your Operations session has expired.'); return; } if (response.status === 403) { setDetailError('You do not have authority to view this user.'); return; } if (response.status === 404) { setDetailError('User profile not found.'); return; } if (!response.ok) { setDetailError('Failed to load user details.'); return; } const data = (await response.json()) as SafeUserDetail; setUserDetail(data); } catch { setDetailError('Unable to connect to Operations platform.'); } finally { setIsLoadingDetail(false); } }, []); function handleSelectUser(targetId: string) { setSelectedUserId(targetId); setActionSuccessMessage(null); void fetchUserDetail(targetId); } function handleCloseDetail() { setSelectedUserId(null); setUserDetail(null); setDetailError(null); setActionType(null); setActionReason(''); setActionError(null); setActionSuccessMessage(null); } function handleOpenAction( type: 'suspend' | 'reinstate' | 'revoke-sessions' | 'reset-password' ) { setActionType(type); setActionReason(''); setActionError(null); } function handleCloseActionModal() { if (isSubmittingAction) return; setActionType(null); setActionReason(''); setActionError(null); } // Handle Action Submit async function handleExecuteAction(e: React.FormEvent) { e.preventDefault(); if (!selectedUserId || !actionType) return; if (actionReason.trim().length < 3) { setActionError('Reason must be at least 3 characters long.'); return; } setIsSubmittingAction(true); setActionError(null); try { let endpoint = ''; let bodyData: Record = {}; if (actionType === 'suspend') { endpoint = `/api/ops/users/${encodeURIComponent(selectedUserId)}/status`; bodyData = { is_active: false, reason: actionReason.trim() }; } else if (actionType === 'reinstate') { endpoint = `/api/ops/users/${encodeURIComponent(selectedUserId)}/status`; bodyData = { is_active: true, reason: actionReason.trim() }; } else if (actionType === 'revoke-sessions') { endpoint = `/api/ops/users/${encodeURIComponent(selectedUserId)}/revoke-sessions`; bodyData = { reason: actionReason.trim() }; } else if (actionType === 'reset-password') { endpoint = `/api/ops/users/${encodeURIComponent(selectedUserId)}/reset-password`; bodyData = { reason: actionReason.trim() }; } const response = await fetch(endpoint, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json', }, body: JSON.stringify(bodyData), }); if (!response.ok) { const errorData = await response.json().catch(() => null); setActionError(errorData?.detail ?? 'Action failed to execute.'); setIsSubmittingAction(false); return; } const result = await response.json(); // Update state locally if (actionType === 'suspend' || actionType === 'reinstate') { const newIsActive = actionType === 'reinstate'; setUserDetail((prev) => prev ? { ...prev, is_active: newIsActive } : null ); setUsers((prev) => prev.map((u) => u.id === selectedUserId ? { ...u, is_active: newIsActive } : u ) ); setActionSuccessMessage( newIsActive ? 'Account reinstated successfully.' : 'Account suspended and active sessions revoked.' ); } else if (actionType === 'revoke-sessions') { setUserDetail((prev) => prev ? { ...prev, sessions: prev.sessions.map((s) => ({ ...s, is_revoked: true })), } : null ); setUsers((prev) => prev.map((u) => u.id === selectedUserId ? { ...u, active_sessions_count: 0 } : u ) ); setActionSuccessMessage('All active user sessions revoked.'); } else if (actionType === 'reset-password') { setActionSuccessMessage( `Password reset link dispatched. Token valid until ${ result.expires_at ? formatDate(result.expires_at) : '24 hours' }.` ); } setActionType(null); setActionReason(''); } catch { setActionError('Network error executing operation.'); } finally { setIsSubmittingAction(false); } } function handleSearchSubmit(e: React.FormEvent) { e.preventDefault(); setAppliedSearch(searchQuery); } function handleClearSearch() { setSearchQuery(''); setAppliedSearch(''); } function formatDate(isoString?: string | null) { if (!isoString) return 'Never'; try { const d = new Date(isoString); return d.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', }); } catch { return isoString; } } return (
{/* Header */}
User Management & Governance

User Management

{totalCount !== null && ( {totalCount} Total )}

Search accounts, inspect verified credentials, audit sessions, and enforce status boundaries.

{/* Filter & Search Bar */}
{/* Search Input */}
search setSearchQuery(e.target.value)} placeholder="Search user by email or account ID..." className="w-full bg-surface-container pl-10 pr-9 py-2.5 rounded-xl border border-outline-variant/30 text-xs sm:text-sm font-mono text-on-surface placeholder:text-outline focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none" /> {searchQuery && ( )}
{/* Filter Pills */}
{/* Status Filter */}
Status:
{(['all', 'active', 'suspended'] as const).map((st) => ( ))}
{/* Verification Filter */}
Verified:
{(['all', 'verified', 'unverified'] as const).map((vf) => ( ))}
{/* Error Banner */} {errorMessage && (
error {errorMessage}
)} {/* Users Content: Desktop Table & Mobile Cards */} {isLoading ? (
progress_activity Loading platform users...
) : users.length === 0 ? (
group_off

No users found

{appliedSearch || statusFilter !== 'all' || verifiedFilter !== 'all' ? 'No users match your search and filter criteria.' : 'No users registered in this environment.'}

) : ( <> {/* Desktop Table */}
{users.map((u) => ( handleSelectUser(u.id)} className="hover:bg-surface-container/50 cursor-pointer transition-colors group focus-within:bg-surface-container" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleSelectUser(u.id); } }} > ))}
User / ID Email Status Account Status App Version / Device Last Sync Sessions Rides Action
{u.email} {u.id}
{u.email_verified ? ( check_circle Verified ) : ( schedule Unverified )} {u.is_active ? ( Active ) : ( Suspended )} {u.latest_app_version ?? ( Unknown )} {formatDate(u.last_sync_at)} {u.active_sessions_count} active {u.total_rides}
{/* Mobile Stacked Cards */}
{users.map((u) => ( ))}
{/* Cursor Pagination */} {nextCursor && (
)} )} {/* User Detail Drawer / Modal */} {selectedUserId && (
{ if (e.key === 'Escape' && !actionType) { handleCloseDetail(); } }} >
{/* Drawer Header */}
account_circle

{userDetail?.email ?? 'User Profile'}

ID: {selectedUserId}

{/* Success Feedback Alert */} {actionSuccessMessage && (
check_circle {actionSuccessMessage}
)} {/* Detail Error */} {detailError && (
error {detailError}
)} {/* Drawer Body */}
{isLoadingDetail ? (
progress_activity Loading user details...
) : userDetail ? ( <> {/* Status Badges & Quick Facts */}
Account
{userDetail.is_active ? ( Active ) : ( Suspended )}
Email
{userDetail.email_verified ? ( Verified ) : ( Unverified )}
Role
{userDetail.role}
Subscription
{userDetail.subscription?.plan ?? 'Free'}
{/* Operational Management Actions */}

Administrative Actions

{userDetail.is_active ? ( ) : ( )}
{/* Account Metadata */}

Account Timestamps

Registered {formatDate(userDetail.created_at)}
Last Updated {formatDate(userDetail.updated_at)}
{/* Rides Summary */}

Rides Summary

Total Rides Recorded {userDetail.ride_summary.total_rides}
Last Ride Timestamp {formatDate(userDetail.ride_summary.last_ride_at)}
{/* Device & Session History */}

Active Sessions & Devices ({userDetail.sessions.length})

{userDetail.sessions.length === 0 ? (

No recorded sessions for this user.

) : (
{userDetail.sessions.map((s) => (
devices {s.device_name ?? 'Unknown Device'}

Created: {formatDate(s.created_at)} | Last used: {formatDate(s.last_used_at)}

{s.is_revoked ? ( Revoked ) : ( Active )}
))}
)}
{/* Recent Audit Log */}

Recent Activity & Security Audit ({userDetail.recent_audit.length})

{userDetail.recent_audit.length === 0 ? (

No audit events recorded for this user.

) : (
{userDetail.recent_audit.map((evt, idx) => (
{evt.action} {evt.ip && ( IP: {evt.ip} )}
{formatDate(evt.timestamp)}
))}
)}
) : null}
)} {/* Confirmation Modal with Reason Input */} {actionType && (
{ if (e.key === 'Escape' && !isSubmittingAction) { handleCloseActionModal(); } }} >
{actionType === 'suspend' ? 'warning' : actionType === 'revoke-sessions' ? 'logout' : actionType === 'reset-password' ? 'lock_reset' : 'check_circle'}

{actionType === 'suspend' && 'Suspend User Account'} {actionType === 'reinstate' && 'Reinstate User Account'} {actionType === 'revoke-sessions' && 'Revoke All User Sessions'} {actionType === 'reset-password' && 'Dispatch Password Reset'}

{actionType === 'suspend' && 'Suspending this user will immediately invalidate all active sessions, block further authentication, and halt telemetry sync until reinstated.'} {actionType === 'reinstate' && 'Reinstating this account will allow the user to log in and resume normal application activity.'} {actionType === 'revoke-sessions' && 'Revoking sessions terminates all active refresh tokens and browser/mobile sessions across all devices.'} {actionType === 'reset-password' && 'Generating a secure reset token creates an emergency password reset link valid for 24 hours.'}

{actionError && (
error {actionError}
)}