admin/features/events/create/VerifiedHostPicker.tsx
alisaza 1612e3575d feat(events): add admin UI to create events for verified hosts
Wire manage-events/new with host picker and create flow against the new
admin create API, including entry points from the events list and user detail.
2026-09-09 11:22:00 +03:30

173 lines
5.0 KiB
TypeScript

'use client'
import { useCallback, useEffect, useState } from 'react'
import Button from '@/components/formElements/Button'
import Input from '@/components/formElements/Input'
import Modal from '@/components/modals/Modal'
import { formatPersonName } from '@/helpers'
import { formatIranianMobile } from '@/lib/formatters'
import { getAdminUsers } from '@/api/generated/admin-users/admin-users'
import type { AdminUserListItemDto } from '@/api/generated/models'
import { unwrapApiPayload } from '@/helpers/listResponse'
import { texts } from '@/texts'
export interface VerifiedHostOption {
id: string
firstName: string | null
lastName: string | null
mobile: string
}
interface VerifiedHostPickerProps {
selected: VerifiedHostOption | null
onSelect: (host: VerifiedHostOption) => void
}
const adminUsersApi = getAdminUsers()
function toHostOption(row: AdminUserListItemDto): VerifiedHostOption {
return {
id: row.id,
firstName: row.firstName,
lastName: row.lastName,
mobile: row.mobile,
}
}
async function searchVerifiedHosts(query: string): Promise<VerifiedHostOption[]> {
const trimmed = query.trim()
const filters: Record<string, string> = { userType: 'host', identityStatus: 'verified' }
if (trimmed) {
if (/^\d+$/.test(trimmed)) {
filters.mobile = trimmed
} else if (trimmed.includes(' ')) {
const [first, ...rest] = trimmed.split(/\s+/)
filters.firstName = first
filters.lastName = rest.join(' ')
} else {
filters.firstName = trimmed
}
}
const response = await adminUsersApi.adminUsersControllerList({
page: 1,
pageSize: 20,
sort: '-createdAt',
filters,
})
const payload = unwrapApiPayload<{ items?: AdminUserListItemDto[] }>(response.data)
const items = Array.isArray(payload.items) ? payload.items : []
return items.map(toHostOption)
}
const VerifiedHostPicker = ({ selected, onSelect }: VerifiedHostPickerProps) => {
const [isOpen, setIsOpen] = useState(false)
const [query, setQuery] = useState('')
const [results, setResults] = useState<VerifiedHostOption[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const load = useCallback(async (nextQuery: string) => {
try {
setIsLoading(true)
setError(null)
setResults(await searchVerifiedHosts(nextQuery))
} catch {
setResults([])
setError(texts.events.createEventHostSearchFailed)
} finally {
setIsLoading(false)
}
}, [])
useEffect(() => {
if (!isOpen) return
const timer = window.setTimeout(() => {
void load(query)
}, 250)
return () => {
window.clearTimeout(timer)
}
}, [isOpen, load, query])
const selectedLabel = selected
? `${formatPersonName(selected.firstName, selected.lastName)} · ${formatIranianMobile(selected.mobile)}`
: texts.events.createEventHostPickerPlaceholder
return (
<>
<Button
fullWidth
className="justify-start"
variant="bordered"
onClick={() => {
setIsOpen(true)
}}
>
{selectedLabel}
</Button>
<Modal
acceptBtnText={texts.common.close}
isOpen={isOpen}
size="2xl"
title={texts.events.createEventHostPickerLabel}
onAccept={() => {
setIsOpen(false)
}}
onOpenChange={setIsOpen}
>
<div className="grid gap-3">
<Input
generalType="input"
label={texts.events.createEventHostSearchPlaceholder}
name="verifiedHostSearch"
value={query}
onValueChange={(value) => {
setQuery(typeof value === 'string' ? value : '')
}}
/>
{error ? <p className="text-sm text-fourth-900">{error}</p> : null}
{isLoading ? <p className="text-sm text-secondary-20">{texts.common.loadingList}</p> : null}
{!isLoading && !error && results.length === 0 ? (
<p className="text-sm text-secondary-20">{texts.events.createEventHostSearchEmpty}</p>
) : null}
<ul className="max-h-80 divide-y divide-secondary-40 overflow-y-auto rounded-xl border border-secondary-40">
{results.map((host) => (
<li key={host.id}>
<Button
className="h-auto w-full flex-col items-stretch gap-1 rounded-none p-3 text-right"
variant="light"
onClick={() => {
onSelect(host)
setIsOpen(false)
}}
>
<span className="text-sm font-semibold text-secondary-10">{formatPersonName(host.firstName, host.lastName)}</span>
<span
className="text-xs text-secondary-20"
dir="ltr"
>
{formatIranianMobile(host.mobile)}
</span>
</Button>
</li>
))}
</ul>
</div>
</Modal>
</>
)
}
export default VerifiedHostPicker