Files
kycnotme/web/src/pages/service-suggestion/index.astro
2025-05-26 18:04:45 +00:00

202 lines
6.4 KiB
Plaintext

---
import { Icon } from 'astro-icon/components'
import { actions } from 'astro:actions'
import { z } from 'astro:content'
import BadgeSmall from '../../components/BadgeSmall.astro'
import BadgeStandardFilter from '../../components/BadgeStandardFilter.astro'
import Button from '../../components/Button.astro'
import MyPicture from '../../components/MyPicture.astro'
import TimeFormatted from '../../components/TimeFormatted.astro'
import Tooltip from '../../components/Tooltip.astro'
import {
getServiceSuggestionStatusInfo,
serviceSuggestionStatuses,
serviceSuggestionStatusesZodEnumBySlug,
serviceSuggestionStatusSlugToId,
} from '../../constants/serviceSuggestionStatus'
import {
getServiceSuggestionTypeInfo,
serviceSuggestionTypes,
serviceSuggestionTypeSlugToId,
serviceSuggestionTypesZodEnumBySlug,
} from '../../constants/serviceSuggestionType'
import BaseLayout from '../../layouts/BaseLayout.astro'
import { cn } from '../../lib/cn'
import { zodParseQueryParamsStoringErrors } from '../../lib/parseUrlFilters'
import { prisma } from '../../lib/prisma'
import { makeLoginUrl } from '../../lib/redirectUrls'
const user = Astro.locals.user
if (!user) {
return Astro.redirect(makeLoginUrl(Astro.url, { message: 'Login to manage service suggestions' }))
}
const { data: filters } = zodParseQueryParamsStoringErrors(
{
serviceId: z.array(z.number().int().positive()),
status: z.array(
serviceSuggestionStatusesZodEnumBySlug.transform((slug) => serviceSuggestionStatusSlugToId(slug))
),
type: z.array(
serviceSuggestionTypesZodEnumBySlug.transform((slug) => serviceSuggestionTypeSlugToId(slug))
),
},
Astro
)
const serviceSuggestions = await Astro.locals.banners.try('Error fetching service suggestions', async () =>
prisma.serviceSuggestion.findMany({
select: {
id: true,
type: true,
status: true,
createdAt: true,
service: {
select: {
id: true,
name: true,
slug: true,
imageUrl: true,
verificationStatus: true,
},
},
},
where: {
id: filters.serviceId.length > 0 ? { in: filters.serviceId } : undefined,
status: filters.status.length > 0 ? { in: filters.status } : undefined,
type: filters.type.length > 0 ? { in: filters.type } : undefined,
userId: user.id,
},
orderBy: {
createdAt: 'desc',
},
})
)
if (!serviceSuggestions) {
return Astro.rewrite('/404')
}
const createResult = Astro.getActionResult(actions.serviceSuggestion.createService)
const success = !!createResult && !createResult.error
---
<BaseLayout
pageTitle="My service suggestions"
description="Manage your service suggestions"
ogImage={{
template: 'generic',
title: 'Service suggestions',
description: 'Manage your service suggestions',
icon: 'ri:service-line',
}}
widthClassName="max-w-screen-md"
breadcrumbs={[
{
name: 'Service suggestions',
url: '/service-suggestion',
},
{
name: 'My suggestions',
},
]}
>
<div
class="xs:flex-row xs:flex-wrap xs:justify-between mb-8 flex flex-col items-center justify-center gap-4 text-center sm:mt-8"
>
<h1 class="font-title text-day-100 text-3xl">Service suggestions</h1>
<Button as="a" href="/service-suggestion/new" label="Create" icon="ri:add-line" />
</div>
{
success && (
<div class="mb-8 rounded-lg border border-green-500/30 bg-green-950 p-4 text-sm text-green-500">
<Icon name="ri:check-line" class="mr-2 inline-block size-4 text-green-500" />
Service suggestion submitted successfully!
</div>
)
}
<div class="mb-6">
<div class="text-day-200 mb-2 font-medium">Filter by:</div>
<div class="flex flex-wrap gap-2">
{
serviceSuggestionTypes.map((type) => (
<BadgeStandardFilter name="type" value={type.slug} label={type.label} icon={type.icon} />
))
}
{
serviceSuggestionStatuses.map((status) => (
<BadgeStandardFilter name="status" value={status.slug} label={status.label} icon={status.icon} />
))
}
</div>
</div>
{
serviceSuggestions.length === 0 ? (
<p class="text-day-400">No suggestions yet.</p>
) : (
<div class="-mx-4 overflow-x-auto px-4">
<div class="grid w-full min-w-min grid-cols-[1fr_auto_auto_auto_auto] place-content-center place-items-center gap-x-4 gap-y-2">
<p class="place-self-start">Service</p>
<p>Type</p>
<p>Status</p>
<p>Created</p>
<p>Actions</p>
{serviceSuggestions.map((suggestion) => {
const typeInfo = getServiceSuggestionTypeInfo(suggestion.type)
const statusInfo = getServiceSuggestionStatusInfo(suggestion.status)
return (
<>
<a
href={`/service/${suggestion.service.slug}`}
class="inline-flex w-full min-w-32 items-center gap-2 hover:underline"
>
<MyPicture
src={suggestion.service.imageUrl}
fallback="service"
alt={suggestion.service.name}
width={32}
height={32}
class="inline-block size-8 min-w-8 shrink-0 rounded-md"
/>
<span class="shrink truncate">{suggestion.service.name}</span>
</a>
<BadgeSmall color={typeInfo.color} text={typeInfo.label} icon={typeInfo.icon} />
<Tooltip
as="span"
text={statusInfo.label}
class={cn(
'border-night-500 bg-night-800 box-content inline-flex h-8 items-center justify-center gap-1 rounded-full border px-2',
statusInfo.iconClass
)}
classNames={{ tooltip: 'md:hidden!' }}
>
<Icon name={statusInfo.icon} class="size-4" />
<span class="hidden md:inline">{statusInfo.label}</span>
</Tooltip>
<TimeFormatted date={suggestion.createdAt} caseType="sentence" prefix={false} />
<Button
as="a"
href={`/service-suggestion/${suggestion.id}`}
label="View"
icon="ri:eye-line"
/>
</>
)
})}
</div>
</div>
)
}
</BaseLayout>