chore: seminarhof customer project
This commit is contained in:
563
apps/app/src/pages/admin.enquiries.tsx
Normal file
563
apps/app/src/pages/admin.enquiries.tsx
Normal file
@@ -0,0 +1,563 @@
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { m, mKey } from '@/i18n/messages'
|
||||
import { useLocale, getLocale } from '@/i18n/config'
|
||||
import { asI18n } from '@pikku/react'
|
||||
import { keepPreviousData, useQueryClient } from '@tanstack/react-query'
|
||||
import { useDebouncedValue } from '@mantine/hooks'
|
||||
import {
|
||||
Alert,
|
||||
Anchor,
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Group,
|
||||
Loader,
|
||||
Modal,
|
||||
MultiSelect,
|
||||
Paper,
|
||||
SegmentedControl,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
TextInput,
|
||||
} from '@pikku/mantine/core'
|
||||
import { Check, Clock, X } from 'lucide-react'
|
||||
import { usePikkuMutation, usePikkuQuery } from '@project/functions-sdk/pikku/api.gen'
|
||||
import { usePageTitle } from '../components/AppLayout'
|
||||
import { RequireAuth } from '../auth'
|
||||
import { fmtDate, fmtDateShort } from '../lib/dates'
|
||||
|
||||
const STATUS_KEYS = ['pending', 'approved', 'declined'] as const
|
||||
type EnquiryStatus = (typeof STATUS_KEYS)[number]
|
||||
|
||||
type EnquiryDateOption = {
|
||||
optionId: string
|
||||
startDate: string
|
||||
endDate: string
|
||||
priority: number
|
||||
}
|
||||
|
||||
type Enquiry = {
|
||||
enquiryId: string
|
||||
contactName: string | null
|
||||
contactEmail: string
|
||||
contactPhone: string | null
|
||||
website: string | null
|
||||
eventName: string
|
||||
eventOutline: string | null
|
||||
description: string | null
|
||||
dateOptions: EnquiryDateOption[]
|
||||
expectedPersons: number | null
|
||||
halfHouse: boolean
|
||||
notes: string | null
|
||||
status: string
|
||||
waitlistedAt: Date | null
|
||||
approvedAt: Date | null
|
||||
declinedAt: Date | null
|
||||
createdAt: Date
|
||||
bookingId: string | null
|
||||
}
|
||||
|
||||
// The SDK's date reviver (transform-date.js) turns any 'YYYY-MM-DD…' response
|
||||
// string into a Date — including these option dates, despite their string type.
|
||||
// Normalize back to a plain 'YYYY-MM-DD' string so the date inputs and equality
|
||||
// checks below work, and parse at local midnight for display.
|
||||
const toYmd = (v: string | Date): string =>
|
||||
v instanceof Date ? v.toISOString().slice(0, 10) : v
|
||||
const optDate = (s: string) => new Date(`${s}T00:00:00`)
|
||||
|
||||
function StatusBadge({ enquiry }: { enquiry: Enquiry }) {
|
||||
useLocale()
|
||||
const color =
|
||||
enquiry.status === 'approved' ? 'green' : enquiry.status === 'declined' ? 'red' : 'blue'
|
||||
return (
|
||||
<Group gap={6} wrap="nowrap">
|
||||
<Badge color={color} variant="light" size="sm">
|
||||
{mKey(`common.pages.admin.enquiries.status.${enquiry.status}` as any)}
|
||||
</Badge>
|
||||
{enquiry.status === 'pending' && enquiry.waitlistedAt && (
|
||||
<Badge color="orange" variant="light" size="sm">
|
||||
{m.common_pages_admin_enquiries_status_waitlisted()}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
function AdminEnquiriesInner() {
|
||||
useLocale()
|
||||
const queryClient = useQueryClient()
|
||||
const navigate = useNavigate()
|
||||
|
||||
usePageTitle(m.common_pages_admin_enquiries_title())
|
||||
|
||||
const [statusFilter, setStatusFilter] = useState<string[]>(['pending'])
|
||||
|
||||
const queryArgs = useMemo(
|
||||
() => ({
|
||||
venueSlug: 'drawehn',
|
||||
status: statusFilter.length ? statusFilter : undefined,
|
||||
}),
|
||||
[statusFilter],
|
||||
)
|
||||
|
||||
const { data, isLoading, isFetching, error } = usePikkuQuery('getEnquiries', queryArgs, {
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['getEnquiries'] })
|
||||
|
||||
// Which enquiry has an open transition modal, and which kind.
|
||||
const [modal, setModal] = useState<{ kind: 'approve' | 'waitlist' | 'decline'; enquiry: Enquiry } | null>(null)
|
||||
|
||||
const statusOptions = useMemo(
|
||||
() =>
|
||||
STATUS_KEYS.map((s) => ({
|
||||
value: s,
|
||||
label: mKey(`common.pages.admin.enquiries.status.${s}` as any),
|
||||
})),
|
||||
[getLocale()],
|
||||
)
|
||||
|
||||
if (isLoading && !data) {
|
||||
return <Container py="xl"><Text>{m.common_states_loading()}</Text></Container>
|
||||
}
|
||||
if (error && !data) {
|
||||
return <Container py="xl"><Text c="red">{m.common_states_error()}</Text></Container>
|
||||
}
|
||||
if (!data) return null
|
||||
|
||||
const enquiries = (data.enquiries as Enquiry[]).map((e) => ({
|
||||
...e,
|
||||
dateOptions: e.dateOptions.map((o) => ({
|
||||
...o,
|
||||
startDate: toYmd(o.startDate),
|
||||
endDate: toYmd(o.endDate),
|
||||
})),
|
||||
}))
|
||||
|
||||
return (
|
||||
<Container size="xl" py="md">
|
||||
<Stack gap="sm">
|
||||
<Group gap="sm" align="flex-end" wrap="wrap">
|
||||
<MultiSelect
|
||||
flex="1 1 240px"
|
||||
clearable
|
||||
placeholder={statusFilter.length ? undefined : m.common_pages_admin_enquiries_filters_status()}
|
||||
data={statusOptions}
|
||||
value={statusFilter}
|
||||
onChange={setStatusFilter}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="sm" c="dimmed">
|
||||
{m.common_pages_admin_enquiries_stats_showing({ count: enquiries.length })}
|
||||
</Text>
|
||||
{isFetching && <Loader size="xs" color="plum" />}
|
||||
</Group>
|
||||
|
||||
<Paper withBorder radius="md" p={0}>
|
||||
{enquiries.length === 0 ? (
|
||||
<Box p="xl">
|
||||
<Text size="sm" c="dimmed" ta="center">
|
||||
{m.common_pages_admin_enquiries_empty()}
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<Table highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>{m.common_pages_admin_enquiries_cols_received()}</Table.Th>
|
||||
<Table.Th>{m.common_pages_admin_enquiries_cols_event()}</Table.Th>
|
||||
<Table.Th>{m.common_pages_admin_enquiries_cols_contact()}</Table.Th>
|
||||
<Table.Th>{m.common_pages_admin_enquiries_cols_dates()}</Table.Th>
|
||||
<Table.Th>{m.common_pages_admin_enquiries_cols_persons()}</Table.Th>
|
||||
<Table.Th>{m.common_pages_admin_enquiries_cols_status()}</Table.Th>
|
||||
<Table.Th>{m.common_pages_admin_enquiries_cols_actions()}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{enquiries.map((e) => (
|
||||
<Table.Tr key={e.enquiryId}>
|
||||
<Table.Td>
|
||||
<Text size="sm">{asI18n(`${fmtDateShort(e.createdAt, getLocale())}`)}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm" fw={500}>{asI18n(`${e.eventName}`)}</Text>
|
||||
{e.eventOutline && (
|
||||
<Text size="xs" c="dimmed" lineClamp={1}>{asI18n(`${e.eventOutline}`)}</Text>
|
||||
)}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm">{asI18n(`${e.contactName ?? '—'}`)}</Text>
|
||||
<Text size="xs" c="dimmed">{asI18n(`${e.contactEmail}`)}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{e.dateOptions.length === 0 ? (
|
||||
<Text size="xs" c="dimmed">{m.common_pages_admin_enquiries_no_dates()}</Text>
|
||||
) : (
|
||||
<Stack gap={2}>
|
||||
{e.dateOptions.map((o, i) => (
|
||||
<Text key={o.optionId} size="sm" c={i === 0 ? undefined : 'dimmed'}>
|
||||
{asI18n(`${fmtDateShort(optDate(o.startDate), getLocale())} → ${fmtDateShort(optDate(o.endDate), getLocale())}`)}
|
||||
</Text>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Table.Td>
|
||||
<Table.Td>{e.expectedPersons ?? '—'}</Table.Td>
|
||||
<Table.Td><StatusBadge enquiry={e} /></Table.Td>
|
||||
<Table.Td>
|
||||
{e.status === 'pending' ? (
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<Button
|
||||
size="compact-xs"
|
||||
color="green"
|
||||
variant="light"
|
||||
leftSection={<Check size={14} />}
|
||||
onClick={() => setModal({ kind: 'approve', enquiry: e })}
|
||||
>
|
||||
{m.common_pages_admin_enquiries_actions_approve()}
|
||||
</Button>
|
||||
<Button
|
||||
size="compact-xs"
|
||||
color="orange"
|
||||
variant="light"
|
||||
leftSection={<Clock size={14} />}
|
||||
onClick={() => setModal({ kind: 'waitlist', enquiry: e })}
|
||||
>
|
||||
{m.common_pages_admin_enquiries_actions_waitlist()}
|
||||
</Button>
|
||||
<Button
|
||||
size="compact-xs"
|
||||
color="red"
|
||||
variant="subtle"
|
||||
leftSection={<X size={14} />}
|
||||
onClick={() => setModal({ kind: 'decline', enquiry: e })}
|
||||
>
|
||||
{m.common_pages_admin_enquiries_actions_decline()}
|
||||
</Button>
|
||||
</Group>
|
||||
) : e.status === 'approved' && e.bookingId ? (
|
||||
<Anchor
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
navigate({ to: '/admin/bookings/$bookingId', params: { bookingId: e.bookingId! } })
|
||||
}
|
||||
>
|
||||
{m.common_pages_admin_enquiries_actions_view_booking()}
|
||||
</Anchor>
|
||||
) : (
|
||||
<Text size="xs" c="dimmed">{asI18n('—')}</Text>
|
||||
)}
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</Paper>
|
||||
</Stack>
|
||||
|
||||
{modal?.kind === 'approve' && (
|
||||
<ApproveModal enquiry={modal.enquiry} onClose={() => setModal(null)} onDone={() => { setModal(null); invalidate() }} />
|
||||
)}
|
||||
{modal?.kind === 'waitlist' && (
|
||||
<WaitlistModal enquiry={modal.enquiry} onClose={() => setModal(null)} onDone={() => { setModal(null); invalidate() }} />
|
||||
)}
|
||||
{modal?.kind === 'decline' && (
|
||||
<DeclineModal enquiry={modal.enquiry} onClose={() => setModal(null)} onDone={() => { setModal(null); invalidate() }} />
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Approve modal ──────────────────────────────────────────────────────────
|
||||
|
||||
function ApproveModal({ enquiry, onClose, onDone }: { enquiry: Enquiry; onClose: () => void; onDone: () => void }) {
|
||||
useLocale()
|
||||
|
||||
const [mode, setMode] = useState<'existing' | 'new'>('existing')
|
||||
// Prefill from the visitor's preferred (first) date option; the admin can
|
||||
// pick a different option or override the inputs entirely.
|
||||
const topOption = enquiry.dateOptions[0]
|
||||
const [startDate, setStartDate] = useState(topOption?.startDate ?? '')
|
||||
const [endDate, setEndDate] = useState(topOption?.endDate ?? '')
|
||||
|
||||
// Existing-client search (server-side via getClients).
|
||||
const [clientId, setClientId] = useState<string | null>(null)
|
||||
const [search, setSearch] = useState('')
|
||||
const [debouncedSearch] = useDebouncedValue(search, 250)
|
||||
const { data: clientData, isFetching: clientsFetching } = usePikkuQuery(
|
||||
'getClients',
|
||||
{ venueSlug: 'drawehn', q: debouncedSearch.trim() || undefined },
|
||||
{ placeholderData: keepPreviousData, enabled: mode === 'existing' },
|
||||
)
|
||||
const clientOptions = useMemo(
|
||||
() =>
|
||||
(clientData?.clients ?? []).map((c) => ({
|
||||
value: c.clientId,
|
||||
label: c.name ? `${c.name}${c.contactEmail ? ` · ${c.contactEmail}` : ''}` : (c.contactEmail ?? c.clientId),
|
||||
})),
|
||||
[clientData],
|
||||
)
|
||||
|
||||
// New-client fields, pre-filled from the enquiry contact.
|
||||
const [name, setName] = useState(enquiry.contactName ?? '')
|
||||
const [email, setEmail] = useState(enquiry.contactEmail)
|
||||
const [phone, setPhone] = useState(enquiry.contactPhone ?? '')
|
||||
const [website, setWebsite] = useState(enquiry.website ?? '')
|
||||
const [billingAddress, setBillingAddress] = useState('')
|
||||
|
||||
const mutation = usePikkuMutation('approveEnquiry', { onSuccess: onDone })
|
||||
|
||||
const datesValid = !!startDate && !!endDate && startDate <= endDate
|
||||
const clientValid = mode === 'existing' ? !!clientId : !!email.trim()
|
||||
const canSubmit = datesValid && clientValid
|
||||
|
||||
const submit = () => {
|
||||
mutation.mutate({
|
||||
enquiryId: enquiry.enquiryId,
|
||||
startDate,
|
||||
endDate,
|
||||
client:
|
||||
mode === 'existing'
|
||||
? { clientId: clientId! }
|
||||
: {
|
||||
name: name.trim() || undefined,
|
||||
contactEmail: email.trim() || undefined,
|
||||
contactPhone: phone.trim() || undefined,
|
||||
website: website.trim() || undefined,
|
||||
billingAddress: billingAddress.trim() || undefined,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal opened onClose={onClose} title={m.common_pages_admin_enquiries_approve_modal_title()} size="lg">
|
||||
<Stack gap="md">
|
||||
<Text size="sm" c="dimmed">{asI18n(`${enquiry.eventName}`)}</Text>
|
||||
|
||||
{enquiry.dateOptions.length > 0 && (
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed" fw={600} tt="uppercase" mb={6}>
|
||||
{m.common_pages_admin_enquiries_approve_modal_date_options()}
|
||||
</Text>
|
||||
<Group gap="xs">
|
||||
{enquiry.dateOptions.map((o, i) => {
|
||||
const selected = o.startDate === startDate && o.endDate === endDate
|
||||
return (
|
||||
<Badge
|
||||
key={o.optionId}
|
||||
variant={selected ? 'filled' : 'light'}
|
||||
color={selected ? 'plum' : 'gray'}
|
||||
style={{ cursor: 'pointer', textTransform: 'none' }}
|
||||
onClick={() => {
|
||||
setStartDate(o.startDate)
|
||||
setEndDate(o.endDate)
|
||||
}}
|
||||
>
|
||||
{asI18n(`${i + 1}. ${fmtDateShort(optDate(o.startDate), getLocale())} → ${fmtDateShort(optDate(o.endDate), getLocale())}`)}
|
||||
</Badge>
|
||||
)
|
||||
})}
|
||||
</Group>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||
<TextInput
|
||||
data-testid="approve-start-date"
|
||||
type="date"
|
||||
label={m.common_pages_admin_enquiries_approve_modal_start_date()}
|
||||
required
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
data-testid="approve-end-date"
|
||||
type="date"
|
||||
label={m.common_pages_admin_enquiries_approve_modal_end_date()}
|
||||
required
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.currentTarget.value)}
|
||||
error={startDate && endDate && startDate > endDate ? m.common_pages_admin_enquiries_approve_modal_date_error() : undefined}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
|
||||
<SegmentedControl
|
||||
fullWidth
|
||||
value={mode}
|
||||
onChange={(v) => setMode(v as 'existing' | 'new')}
|
||||
data={[
|
||||
{ value: 'existing', label: m.common_pages_admin_enquiries_approve_modal_existing_client() },
|
||||
{ value: 'new', label: m.common_pages_admin_enquiries_approve_modal_new_client() },
|
||||
]}
|
||||
/>
|
||||
|
||||
{mode === 'existing' ? (
|
||||
<Select
|
||||
data-testid="approve-client-select"
|
||||
label={m.common_pages_admin_enquiries_approve_modal_select_client()}
|
||||
placeholder={m.common_pages_admin_enquiries_approve_modal_search_client()}
|
||||
searchable
|
||||
data={clientOptions}
|
||||
value={clientId}
|
||||
onChange={setClientId}
|
||||
searchValue={search}
|
||||
onSearchChange={setSearch}
|
||||
rightSection={clientsFetching ? <Loader size="xs" /> : undefined}
|
||||
nothingFoundMessage={m.common_pages_admin_enquiries_approve_modal_no_clients()}
|
||||
filter={({ options }) => options}
|
||||
/>
|
||||
) : (
|
||||
<Stack gap="sm">
|
||||
<TextInput
|
||||
data-testid="approve-new-name"
|
||||
label={m.common_pages_admin_enquiries_approve_modal_client_name()}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.currentTarget.value)}
|
||||
/>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||
<TextInput
|
||||
data-testid="approve-new-email"
|
||||
type="email"
|
||||
label={m.common_pages_admin_enquiries_approve_modal_client_email()}
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label={m.common_pages_admin_enquiries_approve_modal_client_phone()}
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.currentTarget.value)}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
<TextInput
|
||||
label={m.common_pages_admin_enquiries_approve_modal_client_website()}
|
||||
value={website}
|
||||
onChange={(e) => setWebsite(e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label={m.common_pages_admin_enquiries_approve_modal_client_billing_address()}
|
||||
value={billingAddress}
|
||||
onChange={(e) => setBillingAddress(e.currentTarget.value)}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{mutation.error && (
|
||||
<Alert color="red">{asI18n(`${mutation.error.message || m.common_pages_admin_enquiries_transition_error()}`)}</Alert>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={onClose}>{m.common_actions_cancel()}</Button>
|
||||
<Button
|
||||
data-testid="approve-confirm"
|
||||
color="green"
|
||||
onClick={submit}
|
||||
disabled={!canSubmit || mutation.isPending}
|
||||
loading={mutation.isPending}
|
||||
>
|
||||
{m.common_pages_admin_enquiries_actions_approve()}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Waitlist modal ─────────────────────────────────────────────────────────
|
||||
|
||||
function WaitlistModal({ enquiry, onClose, onDone }: { enquiry: Enquiry; onClose: () => void; onDone: () => void }) {
|
||||
useLocale()
|
||||
const mutation = usePikkuMutation('waitlistEnquiry', { onSuccess: onDone })
|
||||
|
||||
const hasOptions = enquiry.dateOptions.length > 0
|
||||
|
||||
return (
|
||||
<Modal opened onClose={onClose} title={m.common_pages_admin_enquiries_waitlist_modal_title()} size="md">
|
||||
<Stack gap="md">
|
||||
<Text size="sm" c="dimmed">{m.common_pages_admin_enquiries_waitlist_modal_intro()}</Text>
|
||||
{hasOptions ? (
|
||||
<Stack gap={4}>
|
||||
{enquiry.dateOptions.map((o, i) => (
|
||||
<Text key={o.optionId} size="sm">
|
||||
{asI18n(`${i + 1}. ${fmtDateShort(optDate(o.startDate), getLocale())} → ${fmtDateShort(optDate(o.endDate), getLocale())}`)}
|
||||
</Text>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Alert color="orange">{m.common_pages_admin_enquiries_waitlist_modal_no_options()}</Alert>
|
||||
)}
|
||||
{mutation.error && (
|
||||
<Alert color="red">{asI18n(`${mutation.error.message || m.common_pages_admin_enquiries_transition_error()}`)}</Alert>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={onClose}>{m.common_actions_cancel()}</Button>
|
||||
<Button
|
||||
data-testid="waitlist-confirm"
|
||||
color="orange"
|
||||
onClick={() => mutation.mutate({ enquiryId: enquiry.enquiryId })}
|
||||
disabled={!hasOptions || mutation.isPending}
|
||||
loading={mutation.isPending}
|
||||
>
|
||||
{m.common_pages_admin_enquiries_actions_waitlist()}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Decline modal ──────────────────────────────────────────────────────────
|
||||
|
||||
function DeclineModal({ enquiry, onClose, onDone }: { enquiry: Enquiry; onClose: () => void; onDone: () => void }) {
|
||||
useLocale()
|
||||
const mutation = usePikkuMutation('declineEnquiry', { onSuccess: onDone })
|
||||
|
||||
return (
|
||||
<Modal opened onClose={onClose} title={m.common_pages_admin_enquiries_decline_modal_title()} size="md">
|
||||
<Stack gap="md">
|
||||
<Text size="sm">
|
||||
{m.common_pages_admin_enquiries_decline_modal_confirm({
|
||||
event: enquiry.eventName,
|
||||
date: enquiry.createdAt ? fmtDate(enquiry.createdAt, getLocale()) : '',
|
||||
})}
|
||||
</Text>
|
||||
{mutation.error && (
|
||||
<Alert color="red">{asI18n(`${mutation.error.message || m.common_pages_admin_enquiries_transition_error()}`)}</Alert>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={onClose}>{m.common_actions_cancel()}</Button>
|
||||
<Button
|
||||
data-testid="decline-confirm"
|
||||
color="red"
|
||||
onClick={() => mutation.mutate({ enquiryId: enquiry.enquiryId })}
|
||||
disabled={mutation.isPending}
|
||||
loading={mutation.isPending}
|
||||
>
|
||||
{m.common_pages_admin_enquiries_actions_decline()}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
function AdminEnquiriesPage() {
|
||||
return (
|
||||
<RequireAuth roles={['admin', 'owner']}>
|
||||
<AdminEnquiriesInner />
|
||||
</RequireAuth>
|
||||
)
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/admin/enquiries')({
|
||||
component: AdminEnquiriesPage,
|
||||
})
|
||||
Reference in New Issue
Block a user