You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
3.9 KiB
143 lines
3.9 KiB
import { useState } from 'react' |
|
import { useTranslation } from 'react-i18next' |
|
import { toast } from 'sonner' |
|
import { |
|
parsePaytoUri, |
|
buildPaytoUri, |
|
getCanonicalPaytoType, |
|
getPaytoTypeInfo, |
|
getPaytoIconChar, |
|
getPaytoLogoPath, |
|
getPaytoProfileUrl, |
|
isKnownPaytoType, |
|
isLightningPaytoType |
|
} from '@/lib/payto' |
|
import PaytoDialog from '@/components/PaytoDialog' |
|
import { HelpCircle } from 'lucide-react' |
|
import { cn } from '@/lib/utils' |
|
|
|
export default function PaytoLink({ |
|
paytoUri, |
|
type: typeProp, |
|
authority: authorityProp, |
|
pubkey, |
|
onOpenZap, |
|
className, |
|
children |
|
}: { |
|
paytoUri?: string |
|
type?: string |
|
authority?: string |
|
/** When set with lightning type, clicking can open Zap dialog via onOpenZap */ |
|
pubkey?: string |
|
onOpenZap?: (pubkey: string) => void |
|
className?: string |
|
children?: React.ReactNode |
|
}) { |
|
const { t } = useTranslation() |
|
const [dialogOpen, setDialogOpen] = useState(false) |
|
|
|
const parsed = paytoUri |
|
? parsePaytoUri(paytoUri) |
|
: typeProp && authorityProp |
|
? { |
|
type: getCanonicalPaytoType(typeProp), |
|
authority: authorityProp, |
|
raw: buildPaytoUri(typeProp, authorityProp) |
|
} |
|
: null |
|
|
|
if (!parsed) { |
|
return children ? <span className={className}>{children}</span> : null |
|
} |
|
|
|
const { type, authority, raw } = parsed |
|
const info = getPaytoTypeInfo(type) |
|
const known = isKnownPaytoType(type) |
|
const isLightning = isLightningPaytoType(type) |
|
const canZap = isLightning && !!pubkey && !!onOpenZap |
|
|
|
const handleClick = (e: React.MouseEvent) => { |
|
e.preventDefault() |
|
e.stopPropagation() |
|
if (canZap) { |
|
onOpenZap(pubkey!) |
|
return |
|
} |
|
if (!known) { |
|
navigator.clipboard.writeText(raw) |
|
toast.success(t('Copied payto address')) |
|
return |
|
} |
|
setDialogOpen(true) |
|
} |
|
|
|
const displayLabel = info?.label ?? type |
|
const categoryLabel = info?.category ? info.category.charAt(0).toUpperCase() + info.category.slice(1) : '' |
|
const logoPath = getPaytoLogoPath(type) |
|
const iconChar = getPaytoIconChar(type) |
|
const profileUrl = getPaytoProfileUrl(type, authority) |
|
const content = children ?? <span className="break-all">{authority}</span> |
|
|
|
const iconEl = ( |
|
<span className="shrink-0 flex items-center justify-center w-4 h-4 text-[1rem] leading-none" aria-hidden> |
|
{logoPath ? ( |
|
<img src={logoPath} alt="" className="size-4 object-contain" /> |
|
) : iconChar != null ? ( |
|
<span className={cn( |
|
'inline-flex items-center justify-center', |
|
isLightning && 'text-yellow-400' |
|
)}> |
|
{iconChar} |
|
</span> |
|
) : ( |
|
<HelpCircle className="size-3.5 text-muted-foreground" /> |
|
)} |
|
</span> |
|
) |
|
|
|
if (profileUrl) { |
|
return ( |
|
<a |
|
href={profileUrl} |
|
target="_blank" |
|
rel="noopener noreferrer" |
|
className={cn( |
|
'text-primary hover:underline cursor-pointer text-left break-words inline-flex items-center gap-1.5', |
|
className |
|
)} |
|
title={categoryLabel ? `${displayLabel} (${categoryLabel}): ${t('Open on website')}` : `${displayLabel}: ${t('Open on website')}`} |
|
onClick={(e) => e.stopPropagation()} |
|
> |
|
{iconEl} |
|
{content} |
|
</a> |
|
) |
|
} |
|
|
|
return ( |
|
<> |
|
<button |
|
type="button" |
|
onClick={handleClick} |
|
className={cn( |
|
'text-primary hover:underline cursor-pointer text-left break-words inline-flex items-center gap-1.5', |
|
className |
|
)} |
|
title={known && categoryLabel ? `${displayLabel} (${categoryLabel}): ${t('Click to open payment options')}` : known ? `${displayLabel}: ${t('Click to open payment options')}` : t('Click to copy address')} |
|
> |
|
{iconEl} |
|
{content} |
|
</button> |
|
{known && ( |
|
<PaytoDialog |
|
open={dialogOpen} |
|
onOpenChange={setDialogOpen} |
|
type={type} |
|
authority={authority} |
|
paytoUri={raw} |
|
/> |
|
)} |
|
</> |
|
) |
|
}
|
|
|