import { useEffect, useMemo, useState } from "react"; import { generateShareLink, getSessionMetadata, getShareAnalytics, localizeSessionVariant, type LocalizedVariantSummary, type ShareAnalyticsSummary, } from "../lib/api"; import { scanSessionForSensitiveData, type PrivacyScanResult } from "../lib/privacyScan"; import { trackWorkflowEvent } from "../lib/usageMetrics"; interface ShareDialogProps { sessionId: string; onClose: () => void; onShared?: () => void; } const styles = { overlay: { position: "fixed" as const, inset: 0, zIndex: 1200, display: "flex", alignItems: "center", justifyContent: "center", padding: "24px", animation: "shareFadeIn 160ms var(--ease-out)", }, backdrop: { position: "absolute" as const, inset: 0, background: "rgba(8, 11, 18, 0.72)", backdropFilter: "blur(10px)", }, card: { position: "relative" as const, width: "min(680px, 100%)", borderRadius: "16px", border: "1px solid var(--border-default)", background: "linear-gradient(165deg, color-mix(in srgb, var(--accent-1) 10%, var(--bg-surface)) 0%, var(--bg-surface) 40%, var(--bg-elevated) 100%)", boxShadow: "0 24px 42px rgba(0, 0, 0, 0.45)", padding: "20px", color: "var(--text-primary)", }, header: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: "12px", }, titleWrap: { minWidth: 0, }, title: { margin: 0, fontSize: "22px", letterSpacing: "-0.02em", lineHeight: 1.15, fontWeight: 760, }, subtitle: { marginTop: "8px", color: "var(--text-secondary)", fontSize: "13px", lineHeight: 1.45, }, closeBtn: { width: "34px", height: "34px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-secondary)", cursor: "pointer", flexShrink: 0, fontSize: "18px", lineHeight: 1, }, body: { marginTop: "16px", display: "flex", flexDirection: "column" as const, gap: "12px", }, section: { border: "1px solid var(--border-default)", borderRadius: "12px", background: "rgba(0, 0, 0, 0.2)", padding: "12px", }, sectionLabel: { margin: 0, fontSize: "11px", fontWeight: 700, textTransform: "uppercase" as const, letterSpacing: "0.05em", color: "var(--text-muted)", }, urlRow: { marginTop: "8px", display: "flex", gap: "8px", alignItems: "center", }, textInput: { flex: 1, height: "38px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", fontSize: "13px", fontFamily: "var(--font-mono)", padding: "0 10px", minWidth: 0, }, compactInput: { width: "96px", height: "38px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", fontSize: "13px", fontFamily: "var(--font-mono)", padding: "0 10px", textTransform: "lowercase" as const, }, checkboxLabel: { display: "inline-flex", alignItems: "center", gap: "8px", height: "38px", padding: "0 10px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-secondary)", fontSize: "12px", fontWeight: 650, }, snippet: { width: "100%", marginTop: "8px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", fontSize: "12px", fontFamily: "var(--font-mono)", padding: "10px", minHeight: "70px", resize: "vertical" as const, }, sectionHint: { marginTop: "6px", fontSize: "12px", color: "var(--text-tertiary)", lineHeight: 1.45, }, actionRow: { marginTop: "10px", display: "flex", flexWrap: "wrap" as const, gap: "8px", }, primaryBtn: { height: "38px", padding: "0 14px", borderRadius: "10px", border: "1px solid color-mix(in srgb, var(--accent-1) 35%, transparent)", background: "var(--accent-1)", color: "#fff", fontSize: "13px", fontWeight: 700, cursor: "pointer", display: "inline-flex", alignItems: "center", gap: "8px", }, secondaryBtn: { height: "38px", padding: "0 14px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", fontSize: "13px", fontWeight: 650, cursor: "pointer", }, footer: { marginTop: "12px", paddingTop: "12px", borderTop: "1px solid var(--border-default)", display: "flex", justifyContent: "space-between", alignItems: "center", gap: "12px", }, footerHint: { fontSize: "12px", color: "var(--text-muted)", lineHeight: 1.45, }, successChip: { display: "inline-flex", alignItems: "center", gap: "6px", padding: "4px 10px", borderRadius: "999px", border: "1px solid var(--status-success)", background: "var(--status-success-muted)", color: "var(--status-success)", fontSize: "11px", fontWeight: 700, textTransform: "uppercase" as const, letterSpacing: "0.05em", }, errorBanner: { borderRadius: "10px", border: "1px solid var(--status-error)", background: "var(--status-error-muted)", color: "var(--status-error)", fontSize: "12px", padding: "10px 12px", }, warningPanel: { borderRadius: "10px", border: "1px solid var(--status-warning)", background: "var(--status-warning-muted)", color: "var(--text-primary)", padding: "10px 12px", }, warningTitle: { fontSize: "12px", fontWeight: 700, color: "var(--status-warning)", marginBottom: "6px", }, warningList: { display: "flex", flexDirection: "column" as const, gap: "6px", fontSize: "12px", color: "var(--text-secondary)", maxHeight: "120px", overflowY: "auto" as const, }, warningItem: { borderRadius: "8px", border: "1px solid var(--border-default)", background: "rgba(0, 0, 0, 0.18)", padding: "6px 8px", fontFamily: "var(--font-mono)", lineHeight: 1.35, }, analyticsGrid: { marginTop: "8px", display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(120px, 1fr))", gap: "8px", }, analyticsCard: { borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", padding: "8px 10px", display: "grid", gap: "2px", }, analyticsLabel: { fontSize: "11px", textTransform: "uppercase" as const, letterSpacing: "0.04em", color: "var(--text-muted)", fontWeight: 700, }, analyticsValue: { fontSize: "18px", lineHeight: 1.1, letterSpacing: "-0.01em", color: "var(--text-primary)", fontWeight: 760, }, analyticsList: { marginTop: "8px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "rgba(0, 0, 0, 0.16)", padding: "8px", display: "grid", gap: "6px", maxHeight: "140px", overflowY: "auto" as const, }, analyticsListItem: { display: "flex", justifyContent: "space-between", gap: "8px", fontSize: "12px", color: "var(--text-secondary)", }, variantStats: { marginTop: "8px", display: "flex", flexWrap: "wrap" as const, gap: "8px", }, variantChip: { borderRadius: "999px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-secondary)", padding: "4px 10px", fontSize: "11px", fontWeight: 700, fontFamily: "var(--font-mono)", }, }; type CopyTarget = | "url" | "markdown" | "embed" | "runtime" | "variant-url" | "variant-markdown" | "variant-embed"; function ShareDialog({ sessionId, onClose, onShared }: ShareDialogProps) { const [sharingUrl, setSharingUrl] = useState(null); const [loading, setLoading] = useState(false); const [scanning, setScanning] = useState(false); const [error, setError] = useState(null); const [copiedTarget, setCopiedTarget] = useState(null); const [privacyScan, setPrivacyScan] = useState(null); const [analytics, setAnalytics] = useState(null); const [analyticsLoading, setAnalyticsLoading] = useState(false); const [analyticsError, setAnalyticsError] = useState(null); const [variantLanguage, setVariantLanguage] = useState("es"); const [variantIncludeVoiceover, setVariantIncludeVoiceover] = useState(true); const [variantLoading, setVariantLoading] = useState(false); const [variantError, setVariantError] = useState(null); const [variantSummary, setVariantSummary] = useState(null); const normalizeLanguage = (raw: string) => raw.trim().toLowerCase(); const shortId = useMemo(() => sessionId.slice(0, 8), [sessionId]); const variantLanguageTag = useMemo(() => { const fromSummary = variantSummary?.target_language ?? ""; const resolved = normalizeLanguage(fromSummary || variantLanguage); return resolved || "es"; }, [variantLanguage, variantSummary]); const embedUrl = useMemo(() => { if (!sharingUrl) return ""; return sharingUrl.replace("/share/", "/embed/"); }, [sharingUrl]); const demoId = useMemo(() => { if (!sharingUrl) return ""; const segments = sharingUrl.split("/").filter(Boolean); return segments.length > 0 ? segments[segments.length - 1] : ""; }, [sharingUrl]); const localizedShareUrl = useMemo(() => { if (!sharingUrl) return ""; const url = new URL(sharingUrl); url.searchParams.set("lang", variantLanguageTag); return url.toString(); }, [sharingUrl, variantLanguageTag]); const localizedEmbedUrl = useMemo(() => { if (!embedUrl) return ""; const url = new URL(embedUrl); url.searchParams.set("lang", variantLanguageTag); return url.toString(); }, [embedUrl, variantLanguageTag]); const markdownSnippet = useMemo(() => { if (!sharingUrl) return ""; return `[ShowRunner session ${shortId}](${sharingUrl})`; }, [sharingUrl, shortId]); const embedSnippet = useMemo(() => { if (!embedUrl) return ""; return ``; }, [embedUrl, shortId]); const runtimeSnippet = useMemo(() => { if (!sharingUrl) return ""; return [ `ShowRunner.mount(\"${demoId}\", {`, ` host: \"${new URL(sharingUrl).origin}\",`, ` mode: \"modal\",`, ` targeting: {`, ` pathContains: \"/pricing\",`, ` selector: \"[data-tour-anchor='pricing']\"`, ` },`, ` identity: { userId: \"user_123\", accountId: \"acme\" },`, ` variables: { first_name: \"Avery\", company: \"Acme\" }`, `});`, ].join("\n"); }, [demoId, sharingUrl]); const localizedMarkdownSnippet = useMemo(() => { if (!localizedShareUrl) return ""; return `[ShowRunner session ${shortId} (${variantLanguageTag.toUpperCase()})](${localizedShareUrl})`; }, [localizedShareUrl, shortId, variantLanguageTag]); const localizedEmbedSnippet = useMemo(() => { if (!localizedEmbedUrl) return ""; return ``; }, [localizedEmbedUrl, shortId, variantLanguageTag]); useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { onClose(); } }; window.addEventListener("keydown", onKeyDown); return () => { window.removeEventListener("keydown", onKeyDown); }; }, [onClose]); useEffect(() => { if (!copiedTarget) return; const timeout = window.setTimeout(() => { setCopiedTarget(null); }, 1800); return () => { window.clearTimeout(timeout); }; }, [copiedTarget]); async function refreshAnalytics() { if (!sharingUrl) return; setAnalyticsLoading(true); setAnalyticsError(null); try { const summary = await getShareAnalytics(sharingUrl); setAnalytics(summary); } catch (err) { setAnalyticsError(String(err)); } finally { setAnalyticsLoading(false); } } useEffect(() => { if (!sharingUrl) { setAnalytics(null); setAnalyticsError(null); setAnalyticsLoading(false); setVariantSummary(null); setVariantError(null); return; } void refreshAnalytics(); const timer = window.setInterval(() => { void refreshAnalytics(); }, 12000); return () => { window.clearInterval(timer); }; }, [sharingUrl]); async function handleGenerateLink(bypassPrivacyCheck = false) { setLoading(true); setError(null); try { if (!bypassPrivacyCheck) { setScanning(true); const metadata = await getSessionMetadata(sessionId); const scanResult = await scanSessionForSensitiveData(metadata); setPrivacyScan(scanResult); if (scanResult.highSeverityCount > 0) { setError( `Potential secrets detected (${scanResult.highSeverityCount}). Review findings below or explicitly continue.` ); return; } } const url = await generateShareLink(sessionId); setSharingUrl(url); trackWorkflowEvent("share_link_generated", { sessionId, metadata: { method: "dialog" }, }); onShared?.(); } catch (err) { setError(`Failed to generate share link: ${String(err)}`); } finally { setScanning(false); setLoading(false); } } async function copyToClipboard(value: string, target: CopyTarget) { try { await navigator.clipboard.writeText(value); setCopiedTarget(target); setError(null); } catch { setError("Clipboard access failed. Copy manually from the fields below."); } } function openPreview() { if (!sharingUrl) return; window.open(sharingUrl, "_blank", "noopener,noreferrer"); } async function handlePublishLocalizedVariant() { if (!sharingUrl) return; const normalizedLanguage = normalizeLanguage(variantLanguage); if (!normalizedLanguage) { setVariantError("Enter a language code such as es, fr, or de."); return; } setVariantLoading(true); setVariantError(null); try { const summary = await localizeSessionVariant(sessionId, normalizedLanguage, { includeVoiceover: variantIncludeVoiceover, overwriteExisting: false, }); setVariantSummary(summary); trackWorkflowEvent("share_localized_variant_published", { sessionId, metadata: { language: summary.target_language, translatedStepFields: summary.translated_step_fields, translatedHotspots: summary.translated_hotspots, generatedVoiceovers: summary.generated_voiceovers, }, }); } catch (err) { setVariantError(`Failed to publish localized variant: ${String(err)}`); } finally { setVariantLoading(false); } } return (

Share Session

Publish session {shortId} and copy the format you need for docs, chat, or internal portals.

{error &&
{error}
} {!sharingUrl && privacyScan && privacyScan.highSeverityCount > 0 && (
Sensitive values were detected in session artifacts.
{privacyScan.findings.slice(0, 4).map((finding, index) => (
{finding.rule}: {finding.snippet}
))}
)} {!sharingUrl && ( )} {sharingUrl && ( <>

Viewer analytics

{analyticsError && (
{analyticsError}
)} {analyticsLoading && !analytics && (
Loading analytics...
)} {analytics && ( <>
Events {analytics.total_events}
Visitors {analytics.unique_visitors}
Accounts {analytics.unique_accounts}
Completions {analytics.completions}
Hotspot Clicks {analytics.hotspot_clicks ?? 0}
Voiceover Plays {analytics.voiceover_plays ?? 0}
{analytics.step_views.length > 0 && (
{analytics.step_views.slice(0, 5).map((stepView) => (
{stepView.step_id} {stepView.views} views
))}
)} )}

Direct link

event.currentTarget.select()} />
Link is hosted from your machine. Keep this session in your library for availability.

Localized Variant

{ setVariantLanguage(event.currentTarget.value); }} placeholder="es" style={styles.compactInput} aria-label="Target language" />
One click localizes step and hotspot copy for this language and publishes a language-scoped share URL.
{variantError && (
{variantError}
)} {variantSummary && ( <>
step fields: {variantSummary.translated_step_fields} hotspots: {variantSummary.translated_hotspots} voiceovers: {variantSummary.generated_voiceovers}
event.currentTarget.select()} />