Release 2025-05-19
This commit is contained in:
195
web/src/pages/admin/service-suggestions/[id].astro
Normal file
195
web/src/pages/admin/service-suggestions/[id].astro
Normal file
@@ -0,0 +1,195 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { actions } from 'astro:actions'
|
||||
|
||||
import Chat from '../../../components/Chat.astro'
|
||||
import ServiceCard from '../../../components/ServiceCard.astro'
|
||||
import { getServiceSuggestionStatusInfo } from '../../../constants/serviceSuggestionStatus'
|
||||
import BaseLayout from '../../../layouts/BaseLayout.astro'
|
||||
import { cn } from '../../../lib/cn'
|
||||
import { parseIntWithFallback } from '../../../lib/numbers'
|
||||
import { prisma } from '../../../lib/prisma'
|
||||
import { makeLoginUrl } from '../../../lib/redirectUrls'
|
||||
|
||||
const user = Astro.locals.user
|
||||
if (!user?.admin) {
|
||||
return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Admin access required' }))
|
||||
}
|
||||
|
||||
const { id: serviceSuggestionIdRaw } = Astro.params
|
||||
const serviceSuggestionId = parseIntWithFallback(serviceSuggestionIdRaw)
|
||||
if (!serviceSuggestionId) {
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
|
||||
const serviceSuggestion = await Astro.locals.banners.try('Error fetching service suggestion', async () =>
|
||||
prisma.serviceSuggestion.findUnique({
|
||||
where: {
|
||||
id: serviceSuggestionId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
notes: true,
|
||||
createdAt: true,
|
||||
type: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
service: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
overallScore: true,
|
||||
kycLevel: true,
|
||||
imageUrl: true,
|
||||
verificationStatus: true,
|
||||
acceptedCurrencies: true,
|
||||
categories: {
|
||||
select: {
|
||||
name: true,
|
||||
icon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
select: {
|
||||
id: true,
|
||||
content: true,
|
||||
createdAt: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
picture: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
if (!serviceSuggestion) {
|
||||
return Astro.rewrite('/404')
|
||||
}
|
||||
|
||||
const statusInfo = getServiceSuggestionStatusInfo(serviceSuggestion.status)
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
pageTitle={`${serviceSuggestion.service.name} | Admin Service Suggestion`}
|
||||
htmx
|
||||
widthClassName="max-w-screen-md"
|
||||
>
|
||||
<div class="mb-4 flex items-center gap-4">
|
||||
<a
|
||||
href="/admin/service-suggestions"
|
||||
class="font-title inline-flex items-center justify-center rounded-md border border-green-500/30 bg-green-500/10 px-3 py-2 text-sm text-green-400 shadow-xs transition-colors duration-200 hover:bg-green-500/20 focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-black focus:outline-hidden"
|
||||
>
|
||||
<Icon name="ri:arrow-left-s-line" class="mr-1 size-4" />
|
||||
Back
|
||||
</a>
|
||||
|
||||
<h1 class="font-title text-xl text-green-500">Service Suggestion</h1>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<ServiceCard service={serviceSuggestion.service} class="mx-auto max-w-full" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-lg border border-green-500/30 bg-black/40 p-4 shadow-[0_0_15px_rgba(34,197,94,0.2)] backdrop-blur-xs"
|
||||
>
|
||||
<h2 class="font-title mb-3 text-lg text-green-500">Suggestion Details</h2>
|
||||
|
||||
<div class="mb-3 grid grid-cols-[auto_1fr] gap-x-3 gap-y-2 text-sm">
|
||||
<span class="font-title text-gray-400">Status:</span>
|
||||
<span
|
||||
class={cn(
|
||||
'inline-flex w-fit items-center rounded-full px-2.5 py-0.5 text-xs font-medium',
|
||||
statusInfo.iconClass
|
||||
)}
|
||||
>
|
||||
<Icon name={statusInfo.icon} class="mr-1 size-3" />
|
||||
{statusInfo.label}
|
||||
</span>
|
||||
|
||||
<span class="font-title text-gray-400">Submitted by:</span>
|
||||
<span class="text-gray-300">
|
||||
<a href={`/admin/users?name=${serviceSuggestion.user.name}`} class="hover:text-green-500">
|
||||
{serviceSuggestion.user.name}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span class="font-title text-gray-400">Submitted at:</span>
|
||||
<span class="text-gray-300">{serviceSuggestion.createdAt.toLocaleString()}</span>
|
||||
|
||||
<span class="font-title text-gray-400">Service page:</span>
|
||||
<a href={`/service/${serviceSuggestion.service.slug}`} class="text-green-400 hover:text-green-500">
|
||||
View Service <Icon
|
||||
name="ri:external-link-line"
|
||||
class="ml-0.5 inline-block size-3 align-[-0.05em]"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{
|
||||
serviceSuggestion.notes && (
|
||||
<div class="mb-4">
|
||||
<h3 class="font-title mb-1 text-sm text-gray-400">Notes from user:</h3>
|
||||
<div class="rounded-md border border-gray-700 bg-black/50 p-3 text-sm whitespace-pre-wrap text-gray-300">
|
||||
{serviceSuggestion.notes}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-lg border border-green-500/30 bg-black/40 p-6 shadow-[0_0_15px_rgba(34,197,94,0.2)] backdrop-blur-xs"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="font-title text-lg text-green-500">Messages</h2>
|
||||
|
||||
<form method="POST" action={actions.admin.serviceSuggestions.update} class="flex gap-2">
|
||||
<input type="hidden" name="suggestionId" value={serviceSuggestion.id} />
|
||||
<select
|
||||
name="status"
|
||||
class="font-title w-full rounded-md border border-green-500/30 bg-black/50 p-2 text-sm text-gray-300 placeholder-gray-500 focus:border-green-500 focus:ring-green-500 disabled:opacity-50"
|
||||
>
|
||||
<option value="PENDING" selected={serviceSuggestion.status === 'PENDING'}> Pending </option>
|
||||
<option value="APPROVED" selected={serviceSuggestion.status === 'APPROVED'}> Approve </option>
|
||||
<option value="REJECTED" selected={serviceSuggestion.status === 'REJECTED'}> Reject </option>
|
||||
<option value="WITHDRAWN" selected={serviceSuggestion.status === 'WITHDRAWN'}> Withdrawn </option>
|
||||
</select>
|
||||
<button
|
||||
type="submit"
|
||||
class="font-title inline-flex items-center justify-center rounded-md border border-green-500/30 bg-green-500/10 px-4 py-2 text-sm text-green-400 shadow-xs transition-colors duration-200 hover:bg-green-500/20 focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-black focus:outline-hidden"
|
||||
>
|
||||
<Icon name="ri:save-line" class="mr-1 size-4" /> Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<Chat
|
||||
messages={serviceSuggestion.messages}
|
||||
userId={user.id}
|
||||
action={actions.admin.serviceSuggestions.message}
|
||||
formData={{
|
||||
suggestionId: serviceSuggestion.id,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
Reference in New Issue
Block a user