199 lines
6.0 KiB
Plaintext
199 lines
6.0 KiB
Plaintext
---
|
|
import { Icon } from 'astro-icon/components'
|
|
|
|
import { currencies } from '../constants/currencies'
|
|
import { serviceVisibilitiesById } from '../constants/serviceVisibility'
|
|
import { verificationStatusesByValue } from '../constants/verificationStatus'
|
|
import { cn } from '../lib/cn'
|
|
import { makeOverallScoreInfo } from '../lib/overallScore'
|
|
|
|
import MyPicture from './MyPicture.astro'
|
|
import Tooltip from './Tooltip.astro'
|
|
|
|
import type { Prisma } from '@prisma/client'
|
|
import type { HTMLAttributes } from 'astro/types'
|
|
|
|
type Props = HTMLAttributes<'article'> & {
|
|
inlineIcons?: boolean
|
|
withoutLink?: boolean
|
|
service: Prisma.ServiceGetPayload<{
|
|
select: {
|
|
name: true
|
|
slug: true
|
|
description: true
|
|
overallScore: true
|
|
privacyScore: true
|
|
trustScore: true
|
|
kycLevel: true
|
|
imageUrl: true
|
|
verificationStatus: true
|
|
serviceVisibility: true
|
|
acceptedCurrencies: true
|
|
categories: {
|
|
select: {
|
|
name: true
|
|
icon: true
|
|
}
|
|
}
|
|
}
|
|
}>
|
|
}
|
|
|
|
const {
|
|
inlineIcons = false,
|
|
service: {
|
|
name = 'Unnamed Service',
|
|
slug,
|
|
description,
|
|
overallScore,
|
|
privacyScore,
|
|
trustScore,
|
|
kycLevel,
|
|
imageUrl,
|
|
categories,
|
|
verificationStatus,
|
|
serviceVisibility,
|
|
acceptedCurrencies,
|
|
},
|
|
class: className,
|
|
withoutLink = false,
|
|
...htmlProps
|
|
} = Astro.props
|
|
|
|
const statusIcon = {
|
|
...verificationStatusesByValue,
|
|
APPROVED: undefined,
|
|
}[verificationStatus]
|
|
|
|
const Element = withoutLink ? 'div' : 'a'
|
|
|
|
const overallScoreInfo = makeOverallScoreInfo(overallScore)
|
|
---
|
|
|
|
<article {...htmlProps}>
|
|
<Element
|
|
href={Element === 'a' ? `/service/${slug}` : undefined}
|
|
aria-label={Element === 'a' ? name : undefined}
|
|
class={cn(
|
|
'border-night-600 group/card bg-night-800 flex flex-col gap-(--gap) rounded-xl border p-(--gap) [--gap:calc(var(--spacing)*3)]',
|
|
(serviceVisibility === 'ARCHIVED' || verificationStatus === 'VERIFICATION_FAILED') &&
|
|
'opacity-75 transition-opacity hover:opacity-100 focus-visible:opacity-100',
|
|
className
|
|
)}
|
|
>
|
|
<!-- Header with Icon and Title -->
|
|
<div class="flex items-center gap-(--gap)">
|
|
<MyPicture
|
|
src={imageUrl}
|
|
fallback="service"
|
|
alt="Logo"
|
|
class={cn(
|
|
'size-12 shrink-0 rounded-sm object-contain text-white',
|
|
(serviceVisibility === 'ARCHIVED' || verificationStatus === 'VERIFICATION_FAILED') &&
|
|
'grayscale-67 transition-all group-hover/card:grayscale-0 group-focus-visible/card:grayscale-0'
|
|
)}
|
|
width={48}
|
|
height={48}
|
|
/>
|
|
|
|
<div class="flex min-w-0 flex-1 flex-col justify-center self-stretch">
|
|
<h1 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 whitespace-nowrap"
|
|
enabled={verificationStatus !== 'VERIFICATION_FAILED'}
|
|
>
|
|
{[
|
|
<Icon
|
|
is:inline={inlineIcons}
|
|
name={statusIcon.icon}
|
|
class={cn(
|
|
'inline-block size-6 shrink-0 rounded-lg p-1 align-[-0.37em]',
|
|
verificationStatus === 'VERIFICATION_FAILED' && 'pr-0',
|
|
statusIcon.classNames.icon
|
|
)}
|
|
/>,
|
|
verificationStatus === 'VERIFICATION_FAILED' && (
|
|
<span class="text-sm font-bold text-red-500">SCAM</span>
|
|
),
|
|
]}
|
|
</Tooltip>
|
|
)
|
|
}{
|
|
serviceVisibility === 'ARCHIVED' && (
|
|
<Tooltip
|
|
text={serviceVisibilitiesById.ARCHIVED.label}
|
|
position="right"
|
|
class="-my-2 shrink-0 whitespace-nowrap"
|
|
>
|
|
<Icon
|
|
is:inline={inlineIcons}
|
|
name={serviceVisibilitiesById.ARCHIVED.icon}
|
|
class={cn(
|
|
'inline-block size-6 shrink-0 rounded-lg p-1 align-[-0.37em]',
|
|
serviceVisibilitiesById.ARCHIVED.iconClass
|
|
)}
|
|
/>
|
|
</Tooltip>
|
|
)
|
|
}
|
|
</h1>
|
|
<div class="max-h-2 flex-1" aria-hidden="true"></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={`${Math.round(privacyScore).toLocaleString()}% Privacy | ${Math.round(trustScore).toLocaleString()}% Trust`}
|
|
>
|
|
{overallScoreInfo.formattedScore}
|
|
</Tooltip>
|
|
|
|
<span class="text-day-300 ml-3 text-sm font-bold whitespace-nowrap">
|
|
KYC {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>
|
|
</article>
|