Post.tsx raw
1 import { cn } from '@/lib/utils'
2 import { ExternalLink } from 'lucide-react'
3 import { memo } from 'react'
4
5 interface PostProps {
6 tweetId: string
7 url: string
8 className?: string
9 embedded?: boolean // kept for API compatibility, now ignored
10 }
11
12 /**
13 * Privacy-preserving X/Twitter post link card.
14 *
15 * Does NOT load Twitter's widgets.js script. Shows a styled card
16 * with the post URL and opens in a new tab when clicked.
17 *
18 * This eliminates all tracking that Twitter's embedded widget performs:
19 * - No platform.twitter.com/widgets.js loading
20 * - No cookies set
21 * - No browser fingerprinting
22 * - No behavioral tracking
23 */
24 const Post = memo(({ tweetId, url, className }: PostProps) => {
25 // Extract username from URL if possible
26 const usernameMatch = url.match(/(?:twitter\.com|x\.com)\/([^/]+)\/status/i)
27 const username = usernameMatch ? usernameMatch[1] : null
28
29 return (
30 <a
31 href={url}
32 target="_blank"
33 rel="noopener noreferrer"
34 onClick={(e) => e.stopPropagation()}
35 className={cn(
36 'block rounded-xl border overflow-hidden cursor-pointer group',
37 'bg-card hover:bg-accent/50 transition-colors',
38 'p-4 flex items-center gap-4',
39 className
40 )}
41 style={{ maxWidth: '550px' }}
42 >
43 {/* X logo */}
44 <div className="flex-shrink-0 bg-black dark:bg-white rounded-full p-3">
45 <svg
46 viewBox="0 0 24 24"
47 className="size-6 text-white dark:text-black"
48 fill="currentColor"
49 >
50 <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
51 </svg>
52 </div>
53
54 {/* Post info */}
55 <div className="flex-1 min-w-0">
56 <div className="flex items-center gap-2">
57 <span className="font-semibold text-foreground">
58 {username ? `@${username}` : 'X Post'}
59 </span>
60 </div>
61 <div className="text-sm text-muted-foreground truncate">
62 Post ID: {tweetId}
63 </div>
64 </div>
65
66 {/* External link indicator */}
67 <div className="flex-shrink-0 opacity-60 group-hover:opacity-100 transition-opacity">
68 <ExternalLink className="size-5" />
69 </div>
70 </a>
71 )
72 })
73
74 Post.displayName = 'XPost'
75
76 export default Post
77