Files
kycnotme/web/src/components/ServiceLinkButton.astro
2025-05-31 18:48:33 +00:00

152 lines
3.9 KiB
Plaintext

---
import { z } from 'astro/zod'
import { Icon } from 'astro-icon/components'
import { networksBySlug } from '../constants/networks'
import { cn } from '../lib/cn'
import type { HTMLAttributes } from 'astro/types'
type Props = Omit<HTMLAttributes<'a'>, 'href' | 'rel' | 'target'> & {
url: string
referral: string | null
enableMinWidth?: boolean
}
const { url: baseUrl, referral, class: className, enableMinWidth = false, ...htmlProps } = Astro.props
function makeLink(url: string, referral: string | null) {
const hostname = new URL(url).hostname
const urlWithReferral = url + (referral ?? '')
const onionMatch = /^(?:https?:\/\/)?(.{0,10}).*?(.{0,10})(\.onion)$/.exec(hostname)
if (onionMatch) {
return {
type: 'onion' as const,
url: urlWithReferral,
textBits: onionMatch.length
? [
{
style: 'normal' as const,
text: onionMatch[1] ?? '',
},
{
style: 'irrelevant' as const,
text: '...',
},
{
style: 'normal' as const,
text: onionMatch[2] ?? '',
},
{
style: 'irrelevant' as const,
text: onionMatch[3] ?? '',
},
]
: [
{
style: 'normal' as const,
text: hostname,
},
],
icon: networksBySlug.onion.icon,
}
}
const i2pMatch = /^(?:https?:\/\/)?(.{0,10}).*?(.{0,8})((?:\.b32)?\.i2p)$/.exec(hostname)
if (i2pMatch) {
return {
type: 'i2p' as const,
url: urlWithReferral,
textBits: i2pMatch.length
? [
{
style: 'normal' as const,
text: i2pMatch[1] ?? '',
},
{
style: 'irrelevant' as const,
text: '...',
},
{
style: 'normal' as const,
text: i2pMatch[2] ?? '',
},
{
style: 'irrelevant' as const,
text: i2pMatch[3] ?? '',
},
]
: [
{
style: 'normal' as const,
text: hostname,
},
],
icon: networksBySlug.i2p.icon,
}
}
const bitcointalkMatch = /^(?:https?:\/\/)?(?:www\.)?bitcointalk\.org$/.exec(hostname)
if (bitcointalkMatch) {
return {
type: 'clearnet' as const,
url: urlWithReferral,
textBits: [
{
style: 'normal',
text: 'BitcoinTalk ',
},
{
style: 'irrelevant',
text: 'thread',
},
],
icon: networksBySlug.clearnet.icon,
}
}
return {
type: 'clearnet' as const,
url: urlWithReferral,
textBits: [
{
style: 'normal' as const,
text: hostname.replace(/^www\./, ''),
},
],
icon: networksBySlug.clearnet.icon,
}
}
const link = makeLink(baseUrl, referral)
if (!z.string().url().safeParse(link.url).success) {
console.error(`Invalid service URL with referral: ${link.url}`)
}
---
<a
href={link.url}
target="_blank"
rel="noopener noreferrer"
class={cn(
'2xs:text-sm 2xs:h-8 2xs:gap-2 focus-visible:ring-offset-night-700 inline-flex h-6 items-center gap-1 rounded-full bg-white text-xs whitespace-nowrap text-black focus-visible:ring-4 focus-visible:ring-orange-500 focus-visible:ring-offset-2 focus-visible:outline-none',
className
)}
{...htmlProps}
>
<Icon name={link.icon} class="2xs:ml-2 2xs:size-5 2xs:-mr-0.5 -mr-0.25 ml-1 size-4" />
<span class={cn('font-title font-bold', { 'min-w-[29ch]': enableMinWidth })}>
{
link.textBits.map((textBit) => (
<span class={cn(textBit.style === 'irrelevant' && 'text-zinc-500')}>{textBit.text}</span>
))
}
</span>
<Icon
name="ri:arrow-right-line"
class="2xs:size-6 mr-1 size-4 rounded-full bg-orange-500 p-0.5 text-white"
/>
</a>