131 lines
5.6 KiB
TypeScript
131 lines
5.6 KiB
TypeScript
import React from 'react'
|
|
import {Pressable, View} from 'react-native'
|
|
import {type AppBskyActorDefs} from '@atproto/api'
|
|
import {useQuery} from '@tanstack/react-query'
|
|
|
|
import {BSKY_SERVICE} from '#/lib/constants'
|
|
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
|
import {atoms as a, useTheme} from '#/alf'
|
|
import {Text} from '#/components/Typography'
|
|
import {createSinglePathSVG} from '#/components/icons/TEMPLATE'
|
|
|
|
// --- SVG Icons (viewBox 0 0 640 640, Font Awesome) ---
|
|
|
|
const GithubIcon = createSinglePathSVG({
|
|
path: 'M237.9 461.4C237.9 463.4 235.6 465 232.7 465C229.4 465.3 227.1 463.7 227.1 461.4C227.1 459.4 229.4 457.8 232.3 457.8C235.3 457.5 237.9 459.1 237.9 461.4zM206.8 456.9C206.1 458.9 208.1 461.2 211.1 461.8C213.7 462.8 216.7 461.8 217.3 459.8C217.9 457.8 216 455.5 213 454.6C210.4 453.9 207.5 454.9 206.8 456.9zM251 455.2C248.1 455.9 246.1 457.8 246.4 460.1C246.7 462.1 249.3 463.4 252.3 462.7C255.2 462 257.2 460.1 256.9 458.1C256.6 456.2 253.9 454.9 251 455.2zM316.8 72C178.1 72 72 177.3 72 316C72 426.9 141.8 521.8 241.5 555.2C254.3 557.5 258.8 549.6 258.8 543.1C258.8 536.9 258.5 502.7 258.5 481.7C258.5 481.7 188.5 496.7 173.8 451.9C173.8 451.9 162.4 422.8 146 415.3C146 415.3 123.1 399.6 147.6 399.9C147.6 399.9 172.5 401.9 186.2 425.7C208.1 464.3 244.8 453.2 259.1 446.6C261.4 430.6 267.9 419.5 275.1 412.9C219.2 406.7 162.8 398.6 162.8 302.4C162.8 274.9 170.4 261.1 186.4 243.5C183.8 237 175.3 210.2 189 175.6C209.9 169.1 258 202.6 258 202.6C278 197 299.5 194.1 320.8 194.1C342.1 194.1 363.6 197 383.6 202.6C383.6 202.6 431.7 169 452.6 175.6C466.3 210.3 457.8 237 455.2 243.5C471.2 261.2 481 275 481 302.4C481 398.9 422.1 406.6 366.2 412.9C375.4 420.8 383.2 435.8 383.2 459.3C383.2 493 382.9 534.7 382.9 542.9C382.9 549.4 387.5 557.3 400.2 555C500.2 521.8 568 426.9 568 316C568 177.3 455.5 72 316.8 72zM169.2 416.9C167.9 417.9 168.2 420.2 169.9 422.1C171.5 423.7 173.8 424.4 175.1 423.1C176.4 422.1 176.1 419.8 174.4 417.9C172.8 416.3 170.5 415.6 169.2 416.9zM158.4 408.8C157.7 410.1 158.7 411.7 160.7 412.7C162.3 413.7 164.3 413.4 165 412C165.7 410.7 164.7 409.1 162.7 408.1C160.7 407.5 159.1 407.8 158.4 408.8zM190.8 444.4C189.2 445.7 189.8 448.7 192.1 450.6C194.4 452.9 197.3 453.2 198.6 451.6C199.9 450.3 199.3 447.3 197.3 445.4C195.1 443.1 192.1 442.8 190.8 444.4zM179.4 429.7C177.8 430.7 177.8 433.3 179.4 435.6C181 437.9 183.7 438.9 185 437.9C186.6 436.6 186.6 434 185 431.7C183.6 429.4 181 428.4 179.4 429.7z',
|
|
viewBox: '0 0 640 640',
|
|
})
|
|
|
|
const XIcon = createSinglePathSVG({
|
|
path: 'M453.2 112L523.8 112L369.6 288.2L551 528L409 528L297.7 382.6L170.5 528L99.8 528L264.7 339.5L90.8 112L236.4 112L336.9 244.9L453.2 112zM428.4 485.8L467.5 485.8L215.1 152L173.1 152L428.4 485.8z',
|
|
viewBox: '0 0 640 640',
|
|
})
|
|
|
|
const YoutubeIcon = createSinglePathSVG({
|
|
path: 'M581.7 188.1C575.5 164.4 556.9 145.8 533.4 139.5C490.9 128 320.1 128 320.1 128C320.1 128 149.3 128 106.7 139.5C83.2 145.8 64.7 164.4 58.4 188.1C47 231 47 320.4 47 320.4C47 320.4 47 409.8 58.4 452.7C64.7 476.3 83.2 494.2 106.7 500.5C149.3 512 320.1 512 320.1 512C320.1 512 490.9 512 533.5 500.5C557 494.2 575.5 476.3 581.8 452.7C593.2 409.8 593.2 320.4 593.2 320.4C593.2 320.4 593.2 231 581.8 188.1zM264.2 401.6L264.2 239.2L406.9 320.4L264.2 401.6z',
|
|
viewBox: '0 0 640 640',
|
|
})
|
|
|
|
// --- Types ---
|
|
|
|
interface LinkItem {
|
|
service: string
|
|
username: string
|
|
}
|
|
|
|
interface LinkCollection {
|
|
links: LinkItem[]
|
|
createdAt: string
|
|
updatedAt?: string
|
|
}
|
|
|
|
// --- Service Config ---
|
|
|
|
const SERVICE_CONFIG: Record<
|
|
string,
|
|
{
|
|
name: string
|
|
urlTemplate: string
|
|
icon: React.ComponentType<{size?: 'xs' | 'sm' | 'md' | 'lg'; fill?: string}>
|
|
}
|
|
> = {
|
|
github: {
|
|
name: 'GitHub',
|
|
urlTemplate: 'https://github.com/{username}',
|
|
icon: GithubIcon,
|
|
},
|
|
x: {
|
|
name: 'X',
|
|
urlTemplate: 'https://x.com/{username}',
|
|
icon: XIcon,
|
|
},
|
|
youtube: {
|
|
name: 'YouTube',
|
|
urlTemplate: 'https://youtube.com/@{username}',
|
|
icon: YoutubeIcon,
|
|
},
|
|
}
|
|
|
|
// --- Component ---
|
|
|
|
export function ProfileAtLinks({
|
|
profile,
|
|
}: {
|
|
profile: AppBskyActorDefs.ProfileViewDetailed
|
|
}) {
|
|
const t = useTheme()
|
|
const openLink = useOpenLink()
|
|
|
|
const {data: linkData} = useQuery({
|
|
queryKey: ['at-links', profile.did],
|
|
queryFn: async () => {
|
|
const res = await fetch(
|
|
`${BSKY_SERVICE}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(profile.did)}&collection=ai.syui.at.link&rkey=self`,
|
|
)
|
|
if (!res.ok) throw new Error('not found')
|
|
const json = await res.json()
|
|
return json.value as LinkCollection
|
|
},
|
|
retry: false,
|
|
staleTime: 1000 * 60 * 5,
|
|
})
|
|
|
|
if (!linkData?.links?.length) return null
|
|
|
|
return (
|
|
<View style={[a.flex_row, a.flex_wrap, a.gap_sm, a.pt_xs]}>
|
|
{linkData.links.map(link => {
|
|
const config = SERVICE_CONFIG[link.service]
|
|
if (!config) return null
|
|
const url = config.urlTemplate.replace('{username}', link.username)
|
|
const Icon = config.icon
|
|
return (
|
|
<Pressable
|
|
key={link.service}
|
|
onPress={() => openLink(url)}
|
|
accessibilityRole="link"
|
|
accessibilityLabel={`${config.name}: ${link.username}`}
|
|
style={[
|
|
a.flex_row,
|
|
a.align_center,
|
|
a.gap_xs,
|
|
a.rounded_full,
|
|
t.atoms.bg_contrast_50,
|
|
{paddingVertical: 6, paddingHorizontal: 10},
|
|
]}>
|
|
<Icon size="xs" fill={t.atoms.text_contrast_medium.color} />
|
|
<Text
|
|
style={[
|
|
a.text_xs,
|
|
a.font_medium,
|
|
t.atoms.text_contrast_medium,
|
|
]}>
|
|
{link.username}
|
|
</Text>
|
|
</Pressable>
|
|
)
|
|
})}
|
|
</View>
|
|
)
|
|
}
|