- npx shadcn add tooltip generates components/ui/tooltip.tsx (official registry, no npm dependency added) - components/phishing/url-list.tsx renders extracted URLs as inert <code> text with copy-to-clipboard only — no <a>/href, no <Link>, no navigating onClick per D-09
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
'use client';
|
|
|
|
/* UrlList — inert, copy-only rendering of URLs extracted from a reported
|
|
* phishing/spam email (REVIEW-03, D-09).
|
|
*
|
|
* D-09 is a deliberately STRICTER-than-sanitization posture: extracted URLs
|
|
* are attacker-controlled content and must never be rendered as anything
|
|
* clickable. There is no anchor tag with a navigation attribute, no
|
|
* `<Link>`, and no navigating `onClick` anywhere in this file — the only
|
|
* affordance is copy-to-clipboard via an icon-only button. Do not "improve"
|
|
* this by adding a real link. */
|
|
|
|
import { Copy } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
interface UrlListProps {
|
|
urls: string[];
|
|
}
|
|
|
|
export function UrlList({ urls }: UrlListProps) {
|
|
if (urls.length === 0) {
|
|
return (
|
|
<p className="text-sm text-muted-foreground">No URLs found in this message.</p>
|
|
);
|
|
}
|
|
|
|
async function handleCopy(url: string) {
|
|
await navigator.clipboard.writeText(url);
|
|
toast.success('Copied');
|
|
}
|
|
|
|
return (
|
|
<ul className="flex flex-col gap-2">
|
|
{urls.map((url, index) => (
|
|
<li key={`${url}-${index}`} className="flex items-center gap-2">
|
|
<code className="font-mono text-xs truncate flex-1">{url}</code>
|
|
<Button
|
|
size="icon"
|
|
variant="ghost"
|
|
aria-label="Copy URL"
|
|
onClick={() => handleCopy(url)}
|
|
>
|
|
<Copy className="h-3.5 w-3.5" />
|
|
</Button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
}
|