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.
173 lines
5.0 KiB
TypeScript
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
|