Files
kycnotme/web/src/components/ServiceCard.astro

155 lines
4.1 KiB
Plaintext
Raw Normal View History

2025-05-19 10:23:36 +00:00
---
import { Icon } from 'astro-icon/components'
import { currencies } from '../constants/currencies'
import { verificationStatusesByValue } from '../constants/verificationStatus'
import { cn } from '../lib/cn'
2025-05-20 01:47:50 +00:00
import { makeOverallScoreInfo } from '../lib/overallScore'
2025-05-19 10:23:36 +00:00
import { transformCase } from '../lib/strings'
2025-05-20 01:47:50 +00:00
import MyPicture from './MyPicture.astro'
2025-05-19 10:23:36 +00:00
import Tooltip from './Tooltip.astro'
import type { Prisma } from '@prisma/client'
import type { HTMLAttributes } from 'astro/types'
type Props = HTMLAttributes<'a'> & {
inlineIcons?: boolean
withoutLink?: boolean
service: Prisma.ServiceGetPayload<{
select: {
name: true
slug: true
description: true
overallScore: true
kycLevel: true
imageUrl: true
verificationStatus: true
acceptedCurrencies: true
categories: {
select: {
name: true
icon: true
}
}
}
}>
}
const {
inlineIcons = false,
service: {
name = 'Unnamed Service',
slug,
description,
overallScore,
kycLevel,
imageUrl,
categories,
verificationStatus,
acceptedCurrencies,
},
class: className,
withoutLink = false,
...aProps
} = Astro.props
const statusIcon = {
...verificationStatusesByValue,
APPROVED: undefined,
}[verificationStatus]
const Element = withoutLink ? 'div' : 'a'
const overallScoreInfo = makeOverallScoreInfo(overallScore)
---
<Element
href={Element === 'a' ? `/service/${slug}` : undefined}
{...aProps}
class={cn(
'border-night-600 bg-night-800 flex flex-col gap-(--gap) rounded-xl border p-(--gap) [--gap:calc(var(--spacing)*3)]',
className
)}
>
<!-- Header with Icon and Title -->
<div class="flex items-center gap-(--gap)">
2025-05-20 01:47:50 +00:00
<MyPicture
src={imageUrl}
fallback="service"
2025-05-19 10:23:36 +00:00
alt={name || 'Service logo'}
class="size-12 shrink-0 rounded-sm object-contain text-white"
width={48}
height={48}
/>
<div class="flex min-w-0 flex-1 flex-col justify-center self-stretch">
<h3 class="font-title text-lg leading-none font-medium tracking-wide text-white">
{name}{
statusIcon && (
<Tooltip text={statusIcon.label} position="right" class="-my-2 shrink-0">
<Icon
is:inline={inlineIcons}
name={statusIcon.icon}
class={cn('inline-block size-6 shrink-0 rounded-lg p-1', statusIcon.classNames.icon)}
/>
</Tooltip>
)
}
</h3>
<div class="max-h-2 flex-1"></div>
<div class="flex items-center gap-4 overflow-hidden mask-r-from-[calc(100%-var(--spacing)*4)]">
{
categories.map((category) => (
<span class="text-day-300 inline-flex shrink-0 items-center gap-1 text-sm leading-none">
<Icon name={category.icon} class="size-4" is:inline={inlineIcons} />
<span>{category.name}</span>
</span>
))
}
</div>
</div>
</div>
<div class="flex-1">
<p class="text-day-400 line-clamp-3 text-sm leading-tight">
{description}
</p>
</div>
<div class="flex items-center justify-start">
<Tooltip
class={cn(
'inline-flex size-6 items-center justify-center rounded-sm text-lg font-bold',
overallScoreInfo.classNameBg
)}
text={`${transformCase(overallScoreInfo.text, 'sentence')} score (${overallScoreInfo.formattedScore}/10)`}
>
{overallScoreInfo.formattedScore}
</Tooltip>
<span class="text-day-300 ml-3 text-sm font-bold whitespace-nowrap">
KYC &nbsp;{kycLevel.toLocaleString()}
</span>
<div class="-m-1 ml-auto flex">
{
currencies.map((currency) => {
const isAccepted = acceptedCurrencies.includes(currency.id)
return (
<Tooltip text={currency.name}>
<Icon
is:inline={inlineIcons}
name={currency.icon}
class={cn('text-day-600 box-content size-4 p-1', { 'text-white': isAccepted })}
/>
</Tooltip>
)
})
}
</div>
</div>
</Element>