feat: add member removal functionality with confirmation and error handling
This commit is contained in:
parent
bc7ff54380
commit
0f4844abb8
@ -73,6 +73,8 @@ function PoolManagePageInner() {
|
|||||||
const [hasSearched, setHasSearched] = React.useState(false)
|
const [hasSearched, setHasSearched] = React.useState(false)
|
||||||
const [selectedCandidates, setSelectedCandidates] = React.useState<Set<string>>(new Set())
|
const [selectedCandidates, setSelectedCandidates] = React.useState<Set<string>>(new Set())
|
||||||
const [savingMembers, setSavingMembers] = React.useState(false)
|
const [savingMembers, setSavingMembers] = React.useState(false)
|
||||||
|
const [removingMemberId, setRemovingMemberId] = React.useState<string | null>(null)
|
||||||
|
const [removeError, setRemoveError] = React.useState<string>('')
|
||||||
|
|
||||||
async function fetchMembers() {
|
async function fetchMembers() {
|
||||||
if (!token || !poolId || poolId === 'pool-unknown') return
|
if (!token || !poolId || poolId === 'pool-unknown') return
|
||||||
@ -211,6 +213,23 @@ function PoolManagePageInner() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeMember(userId: string) {
|
||||||
|
if (!token || !poolId || poolId === 'pool-unknown') return
|
||||||
|
const user = users.find(u => u.id === userId)
|
||||||
|
const label = user?.name || user?.email || 'this user'
|
||||||
|
if (!window.confirm(`Remove ${label} from this pool?`)) return
|
||||||
|
setRemoveError('')
|
||||||
|
setRemovingMemberId(userId)
|
||||||
|
try {
|
||||||
|
await AdminAPI.removePoolMembers(token, poolId, [userId])
|
||||||
|
await fetchMembers()
|
||||||
|
} catch (e: any) {
|
||||||
|
setRemoveError(e?.message || 'Failed to remove user from pool.')
|
||||||
|
} finally {
|
||||||
|
setRemovingMemberId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageTransitionEffect>
|
<PageTransitionEffect>
|
||||||
<div className="min-h-screen flex flex-col bg-gradient-to-tr from-blue-50 via-white to-blue-100">
|
<div className="min-h-screen flex flex-col bg-gradient-to-tr from-blue-50 via-white to-blue-100">
|
||||||
@ -302,6 +321,11 @@ function PoolManagePageInner() {
|
|||||||
Add User
|
Add User
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{removeError && (
|
||||||
|
<div className="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||||
|
{removeError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{users.map(u => (
|
{users.map(u => (
|
||||||
<article key={u.id} className="rounded-2xl bg-white border border-gray-100 shadow p-5 flex flex-col">
|
<article key={u.id} className="rounded-2xl bg-white border border-gray-100 shadow p-5 flex flex-col">
|
||||||
@ -325,6 +349,15 @@ function PoolManagePageInner() {
|
|||||||
{new Date(u.joinedAt).toLocaleDateString('de-DE', { year: 'numeric', month: 'short', day: '2-digit' })}
|
{new Date(u.joinedAt).toLocaleDateString('de-DE', { year: 'numeric', month: 'short', day: '2-digit' })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-4 flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={() => removeMember(u.id)}
|
||||||
|
disabled={removingMemberId === u.id}
|
||||||
|
className="px-3 py-2 text-xs font-medium rounded-lg border border-red-200 bg-red-50 text-red-700 hover:bg-red-100 transition disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{removingMemberId === u.id ? 'Removing…' : 'Remove'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
{membersLoading && (
|
{membersLoading && (
|
||||||
@ -392,7 +425,7 @@ function PoolManagePageInner() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => { setQuery(''); setCandidates([]); setHasSearched(false); setError(''); }}
|
onClick={() => { setQuery(''); setError(''); }}
|
||||||
className="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 transition"
|
className="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 transition"
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
|
|||||||
@ -81,6 +81,8 @@ export default function PoolManagementPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleArchive(poolId: string) {
|
async function handleArchive(poolId: string) {
|
||||||
|
const confirmed = window.confirm('Archive this pool? Users will no longer be able to join or use it.')
|
||||||
|
if (!confirmed) return
|
||||||
setArchiveError('')
|
setArchiveError('')
|
||||||
const res = await setPoolInactive(poolId)
|
const res = await setPoolInactive(poolId)
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@ -91,6 +93,8 @@ export default function PoolManagementPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSetActive(poolId: string) {
|
async function handleSetActive(poolId: string) {
|
||||||
|
const confirmed = window.confirm('Unarchive this pool and make it active again?')
|
||||||
|
if (!confirmed) return
|
||||||
setArchiveError('')
|
setArchiveError('')
|
||||||
const res = await setPoolActive(poolId)
|
const res = await setPoolActive(poolId)
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user