← back to Dear Bubbe Admin
frontend/src/components/UsersPage.js
323 lines
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const UsersPage = () => {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
const [selectedUser, setSelectedUser] = useState(null)
const [filters, setFilters] = useState({
search: '',
blocked: '',
riskLevel: '',
country: ''
})
useEffect(() => {
fetchUsers()
}, [filters])
const fetchUsers = async () => {
try {
setLoading(true)
const params = new URLSearchParams()
Object.entries(filters).forEach(([key, value]) => {
if (value) params.append(key, value)
})
const response = await axios.get(`/api/users?${params.toString()}`)
setUsers(response.data.users || [])
} catch (error) {
console.error('Failed to fetch users:', error)
} finally {
setLoading(false)
}
}
const fetchUserDetails = async (userId) => {
try {
const response = await axios.get(`/api/users/${userId}`)
setSelectedUser(response.data)
} catch (error) {
console.error('Failed to fetch user details:', error)
}
}
const blockUser = async (userId, reason) => {
try {
await axios.post(`/api/users/${userId}/block`, { reason })
fetchUsers()
setSelectedUser(null)
alert('User blocked successfully')
} catch (error) {
console.error('Failed to block user:', error)
alert('Failed to block user')
}
}
const unblockUser = async (userId) => {
try {
await axios.post(`/api/users/${userId}/unblock`)
fetchUsers()
setSelectedUser(null)
alert('User unblocked successfully')
} catch (error) {
console.error('Failed to unblock user:', error)
alert('Failed to unblock user')
}
}
if (loading) {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: '40px' }}>
<div className="loading-spinner"></div>
<span style={{ marginLeft: '12px' }}>Loading users...</span>
</div>
)
}
return (
<div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '24px'
}}>
<h2>User Management</h2>
<button onClick={fetchUsers} className="btn btn-primary">
Refresh
</button>
</div>
{/* Filters */}
<div className="card" style={{ marginBottom: '24px' }}>
<div className="card-content">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '16px' }}>
<input
type="text"
placeholder="Search users..."
className="form-input"
value={filters.search}
onChange={(e) => setFilters({...filters, search: e.target.value})}
/>
<select
className="form-select"
value={filters.blocked}
onChange={(e) => setFilters({...filters, blocked: e.target.value})}
>
<option value="">All Users</option>
<option value="false">Active Users</option>
<option value="true">Blocked Users</option>
</select>
<select
className="form-select"
value={filters.riskLevel}
onChange={(e) => setFilters({...filters, riskLevel: e.target.value})}
>
<option value="">All Risk Levels</option>
<option value="critical">Critical</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
</div>
</div>
</div>
{/* Users Table */}
<div className="card">
<div className="card-content">
<div className="table-container">
<table className="table">
<thead>
<tr>
<th>User</th>
<th>Email</th>
<th>Location</th>
<th>Messages</th>
<th>Risk Level</th>
<th>Status</th>
<th>Last Visit</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.userId}>
<td>
<div>
<strong>{user.profile?.name || user.profile?.preferredName || 'Unknown'}</strong>
<div style={{ fontSize: '12px', color: '#64748b' }}>
{user.userId}
</div>
</div>
</td>
<td>{user.email || 'No email'}</td>
<td>
{user.location?.city && user.location?.country ?
`${user.location.city}, ${user.location.country}` :
'Unknown'
}
</td>
<td>{user.stats?.totalMessages || 0}</td>
<td>
<span className={`badge badge-${user.security?.riskLevel || 'low'}`}>
{user.security?.riskLevel || 'low'}
</span>
</td>
<td>
<span className={`badge ${user.security?.blocked ? 'badge-blocked' : 'badge-active'}`}>
{user.security?.blocked ? 'Blocked' : 'Active'}
</span>
</td>
<td style={{ fontSize: '12px', color: '#64748b' }}>
{user.stats?.lastVisit ?
new Date(user.stats.lastVisit).toLocaleString() :
'Never'
}
</td>
<td>
<button
onClick={() => fetchUserDetails(user.userId)}
className="btn btn-small btn-primary"
style={{ marginRight: '8px' }}
>
View
</button>
{user.security?.blocked ? (
<button
onClick={() => unblockUser(user.userId)}
className="btn btn-small btn-secondary"
>
Unblock
</button>
) : (
<button
onClick={() => {
const reason = prompt('Reason for blocking:')
if (reason) blockUser(user.userId, reason)
}}
className="btn btn-small btn-danger"
>
Block
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
{/* User Details Modal */}
{selectedUser && (
<div className="live-alert-modal" onClick={() => setSelectedUser(null)}>
<div
className="live-alert-content"
style={{ maxWidth: '800px', maxHeight: '80vh', overflow: 'auto' }}
onClick={e => e.stopPropagation()}
>
<div className="alert-header">
<h3>User Details</h3>
<button onClick={() => setSelectedUser(null)}>×</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
<div>
<h4>Profile Information</h4>
<div style={{ background: '#f8fafc', padding: '16px', borderRadius: '8px' }}>
<div><strong>Name:</strong> {selectedUser.user?.profile?.name || 'Unknown'}</div>
<div><strong>Email:</strong> {selectedUser.user?.email || 'No email'}</div>
<div><strong>Age:</strong> {selectedUser.user?.profile?.age || 'Unknown'}</div>
<div><strong>Location:</strong> {
selectedUser.user?.location?.city ?
`${selectedUser.user.location.city}, ${selectedUser.user.location.country}` :
'Unknown'
}</div>
<div><strong>Relationship:</strong> {selectedUser.user?.profile?.relationshipStatus || 'Unknown'}</div>
<div><strong>Job:</strong> {selectedUser.user?.profile?.jobTitle || 'Unknown'}</div>
</div>
</div>
<div>
<h4>Security & Activity</h4>
<div style={{ background: '#f8fafc', padding: '16px', borderRadius: '8px' }}>
<div><strong>Risk Level:</strong>
<span className={`badge badge-${selectedUser.user?.security?.riskLevel || 'low'}`} style={{ marginLeft: '8px' }}>
{selectedUser.user?.security?.riskLevel || 'low'}
</span>
</div>
<div><strong>Status:</strong>
<span className={`badge ${selectedUser.user?.security?.blocked ? 'badge-blocked' : 'badge-active'}`} style={{ marginLeft: '8px' }}>
{selectedUser.user?.security?.blocked ? 'Blocked' : 'Active'}
</span>
</div>
<div><strong>Total Messages:</strong> {selectedUser.user?.stats?.totalMessages || 0}</div>
<div><strong>Total Sessions:</strong> {selectedUser.sessions?.length || 0}</div>
<div><strong>Alerts:</strong> {selectedUser.alerts?.length || 0}</div>
</div>
</div>
</div>
{selectedUser.alerts && selectedUser.alerts.length > 0 && (
<div style={{ marginTop: '20px' }}>
<h4>Recent Alerts</h4>
<div style={{ maxHeight: '200px', overflow: 'auto' }}>
{selectedUser.alerts.slice(0, 5).map((alert, index) => (
<div key={index} style={{
padding: '12px',
margin: '8px 0',
background: '#fef2f2',
border: '1px solid #fecaca',
borderRadius: '6px'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span className={`badge badge-${alert.severity}`}>{alert.type}</span>
<span style={{ fontSize: '12px', color: '#64748b' }}>
{new Date(alert.timestamp).toLocaleString()}
</span>
</div>
{alert.violation?.match && (
<div style={{ marginTop: '8px', fontSize: '14px', fontFamily: 'monospace' }}>
"{alert.violation.match}"
</div>
)}
</div>
))}
</div>
</div>
)}
<div style={{ marginTop: '20px', display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
<button onClick={() => setSelectedUser(null)} className="btn btn-secondary">
Close
</button>
{selectedUser.user?.security?.blocked ? (
<button
onClick={() => unblockUser(selectedUser.user.userId)}
className="btn btn-primary"
>
Unblock User
</button>
) : (
<button
onClick={() => {
const reason = prompt('Reason for blocking:')
if (reason) blockUser(selectedUser.user.userId, reason)
}}
className="btn btn-danger"
>
Block User
</button>
)}
</div>
</div>
</div>
)}
</div>
)
}
export default UsersPage