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