"use client"; import * as React from "react"; import { AlertTriangle, ExternalLink, Loader2, Pencil, RefreshCw, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import type { ViewConfig } from "@/lib/hooks/use-view-data"; import { cn } from "@/lib/utils"; /** Allowed schemes for embed src; rejects javascript:, data:, etc. */ export function sanitizeEmbedUrl(raw: string): string | null { const trimmed = raw.trim(); if (!trimmed) return null; let parsed: URL; try { parsed = new URL(trimmed); } catch { return null; } if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { return null; } // href is serialized safely for iframe src (no HTML/script injection) return parsed.href; } const EXAMPLE_SERVICES = [ "Figma", "Google Docs", "Miro", "Loom", "YouTube", ] as const; export interface EmbedViewProps { config: ViewConfig & { embedUrl?: string }; className?: string; } export function EmbedView({ config, className }: EmbedViewProps) { const [sessionUrl, setSessionUrl] = React.useState(null); const [draft, setDraft] = React.useState(""); const [validationError, setValidationError] = React.useState( null, ); const effectiveUrl = config.embedUrl ?? sessionUrl; React.useEffect(() => { if (config.embedUrl) { setDraft(config.embedUrl); } }, [config.embedUrl]); const handlePreview = () => { setValidationError(null); const safe = sanitizeEmbedUrl(draft); if (!safe) { setValidationError( "Enter a valid URL starting with http:// or https://", ); return; } setSessionUrl(safe); }; const handleChangeUrl = () => { setSessionUrl(null); setDraft(effectiveUrl ?? ""); setValidationError(null); }; if (!effectiveUrl) { return (

Embed a page

Paste a link to show it inside this view. Works best with tools that allow embedding.

{ setDraft(e.target.value); setValidationError(null); }} onKeyDown={(e) => { if (e.key === "Enter") handlePreview(); }} className="text-left" aria-invalid={!!validationError} />
{validationError ? (

{validationError}

) : null}

Often works well with

{EXAMPLE_SERVICES.map((label) => ( {label} ))}
Many sites block embedding in iframes for security. If you see a blank page or an error, the site may not allow this view—use{" "} Open in new tab{" "} after loading.
); } return ( ); } interface EmbedFrameProps { url: string; onChangeUrl: () => void; className?: string; } function EmbedFrame({ url, onChangeUrl, className }: EmbedFrameProps) { const [loadState, setLoadState] = React.useState< "loading" | "loaded" | "error" >("loading"); const [reloadToken, setReloadToken] = React.useState(0); const safeUrl = sanitizeEmbedUrl(url); const iframeSrc = safeUrl ?? ""; React.useEffect(() => { if (!safeUrl) { setLoadState("error"); return; } setLoadState("loading"); }, [safeUrl, reloadToken]); const handleRefresh = () => { setLoadState("loading"); setReloadToken((x) => x + 1); }; const displayUrl = safeUrl ?? url; const truncated = displayUrl.length > 64 ? `${displayUrl.slice(0, 61)}…` : displayUrl; return (
{truncated}
{!safeUrl ? (
Invalid or unsupported URL. Use Change URL to enter an http(s) link.
) : ( <> {loadState === "loading" ? (
Loading embed
) : null} {loadState === "error" ? (

Could not load this page in the embed.

The URL may be invalid, blocked, or the site may forbid framing. Try opening in a new tab or a different URL.

) : null} {loadState !== "error" ? (