lomavuokraus/app/admin/users/page.tsx
2025-11-24 17:15:20 +02:00

196 lines
6 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useI18n } from '../../components/I18nProvider';
type UserRow = {
id: string;
email: string;
name: string | null;
role: string;
status: string;
emailVerifiedAt: string | null;
approvedAt: string | null;
};
const roleOptions = ['USER', 'USER_MODERATOR', 'LISTING_MODERATOR', 'ADMIN'];
export default function AdminUsersPage() {
const { t } = useI18n();
const [users, setUsers] = useState<UserRow[]>([]);
const [error, setError] = useState<string | null>(null);
const [message, setMessage] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
async function load() {
setError(null);
try {
const res = await fetch('/api/admin/users', { cache: 'no-store' });
const data = await res.json();
if (!res.ok) {
setError(data.error || 'Failed to load users');
} else {
setUsers(data.users ?? []);
}
} catch (e) {
setError('Failed to load users');
}
}
useEffect(() => {
load();
}, []);
async function setRole(userId: string, role: string) {
setMessage(null);
setError(null);
setLoading(true);
try {
const res = await fetch('/api/admin/users/role', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, role }),
});
const data = await res.json();
if (!res.ok) {
setError(data.error || 'Failed to update role');
} else {
setMessage(t('userUpdated'));
load();
}
} catch (e) {
setError('Failed to update role');
} finally {
setLoading(false);
}
}
async function approve(userId: string) {
setMessage(null);
setError(null);
setLoading(true);
try {
const res = await fetch('/api/admin/users/approve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId }),
});
const data = await res.json();
if (!res.ok) {
setError(data.error || 'Failed to approve user');
} else {
setMessage(t('userUpdated'));
load();
}
} catch (e) {
setError('Failed to approve user');
} finally {
setLoading(false);
}
}
async function reject(userId: string) {
setMessage(null);
setError(null);
setLoading(true);
try {
const reason = window.prompt('Reason for rejection? (optional)');
const res = await fetch('/api/admin/users/reject', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, reason }),
});
const data = await res.json();
if (!res.ok) {
setError(data.error || 'Failed to reject user');
} else {
setMessage(t('userUpdated'));
load();
}
} catch (e) {
setError('Failed to reject user');
} finally {
setLoading(false);
}
}
async function remove(userId: string) {
setMessage(null);
setError(null);
setLoading(true);
try {
const reason = window.prompt('Reason for removal? (optional)');
const res = await fetch('/api/admin/users/remove', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, reason }),
});
const data = await res.json();
if (!res.ok) {
setError(data.error || 'Failed to remove user');
} else {
setMessage(t('userUpdated'));
load();
}
} catch (e) {
setError('Failed to remove user');
} finally {
setLoading(false);
}
}
return (
<main className="panel" style={{ maxWidth: 960, margin: '40px auto' }}>
<h1>{t('adminUsersTitle')}</h1>
<p>{t('adminUsersLead')}</p>
{message ? <p style={{ color: 'green' }}>{message}</p> : null}
{error ? <p style={{ color: 'red' }}>{error}</p> : null}
<table style={{ width: '100%', borderCollapse: 'collapse', marginTop: 12 }}>
<thead>
<tr>
<th style={{ textAlign: 'left', padding: 8 }}>{t('tableEmail')}</th>
<th style={{ textAlign: 'left', padding: 8 }}>{t('tableRole')}</th>
<th style={{ textAlign: 'left', padding: 8 }}>{t('tableStatus')}</th>
<th style={{ textAlign: 'left', padding: 8 }}>{t('tableVerified')}</th>
<th style={{ textAlign: 'left', padding: 8 }}>{t('tableApproved')}</th>
<th style={{ textAlign: 'left', padding: 8 }}>Actions</th>
</tr>
</thead>
<tbody>
{users.map((u) => (
<tr key={u.id} style={{ borderTop: '1px solid #eee' }}>
<td style={{ padding: 8 }}>{u.email}</td>
<td style={{ padding: 8 }}>
<select value={u.role} onChange={(e) => setRole(u.id, e.target.value)} disabled={loading}>
{roleOptions.map((r) => (
<option key={r} value={r}>
{r}
</option>
))}
</select>
</td>
<td style={{ padding: 8 }}>{u.status}</td>
<td style={{ padding: 8 }}>{u.emailVerifiedAt ? 'yes' : 'no'}</td>
<td style={{ padding: 8 }}>{u.approvedAt ? 'yes' : 'no'}</td>
<td style={{ padding: 8 }}>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{u.approvedAt ? null : (
<button className="button secondary" onClick={() => approve(u.id)} disabled={loading}>
{t('approve')}
</button>
)}
<button className="button secondary" onClick={() => reject(u.id)} disabled={loading}>
{t('reject')}
</button>
<button className="button secondary" onClick={() => remove(u.id)} disabled={loading}>
{t('remove')}
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</main>
);
}