Spaces:
Runtime error
Runtime error
File size: 6,167 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | "use client";
// src/app/(dashboard)/dashboard/playground/components/ExportCodeModal.tsx
import { useState, useEffect, useCallback } from "react";
import { useTranslations } from "next-intl";
import type { PlaygroundState, ExportLanguage } from "@/lib/playground/codeExport";
import { exportAllLanguages, API_KEY_PLACEHOLDER } from "@/lib/playground/codeExport";
interface ExportCodeModalProps {
state: PlaygroundState;
onClose: () => void;
}
const LANGUAGE_TABS: Array<{ id: ExportLanguage; label: string }> = [
{ id: "curl", label: "curl" },
{ id: "python", label: "Python" },
{ id: "typescript", label: "TypeScript" },
];
/**
* ExportCodeModal — shows curl / Python / TypeScript snippets for the current playground state.
*
* Security: always uses API_KEY_PLACEHOLDER ("$OMNIROUTE_API_KEY") — never a real key (D11 / Hard Rule #1).
*/
export default function ExportCodeModal({ state, onClose }: ExportCodeModalProps) {
const t = useTranslations("playground");
const [activeLanguage, setActiveLanguage] = useState<ExportLanguage>("curl");
const [copied, setCopied] = useState(false);
// Generate all snippets once (state is passed in from parent, not re-fetched).
const snippets = exportAllLanguages(state);
const currentCode = snippets[activeLanguage];
// Verify that no real API key is embedded (assertion — Hard Rule #1 / D11).
// The regex checks for typical API key patterns (sk-, or other 16+ char alphanumeric strings
// that are NOT the placeholder).
const hasRealKey = /sk-[A-Za-z0-9_-]{16,}/.test(currentCode);
const handleCopy = useCallback(async () => {
if (hasRealKey) return; // Never copy if somehow a real key slipped through
try {
await navigator.clipboard.writeText(currentCode);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// Clipboard API unavailable (e.g. insecure context) — fail silently
}
}, [currentCode, hasRealKey]);
// Close on Escape
useEffect(() => {
function onKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") {
onClose();
}
}
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [onClose]);
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onClick={onClose}
role="dialog"
aria-modal="true"
aria-label={t("exportCode")}
>
<div
className="bg-surface border border-border rounded-xl w-[640px] max-w-[96vw] max-h-[80vh] flex flex-col shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
{/* Modal header */}
<div className="flex items-center justify-between px-5 py-3.5 border-b border-border shrink-0">
<div className="flex items-center gap-2">
<span className="font-mono text-sm text-text-muted"></></span>
<h2 className="text-sm font-semibold text-text-main">{t("exportCodeTitle")}</h2>
</div>
<button
onClick={onClose}
className="p-1 rounded text-text-muted hover:text-text-main hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
aria-label={t("closeExportModal")}
>
<span className="material-symbols-outlined text-[18px]">close</span>
</button>
</div>
{/* Language tabs */}
<div className="flex items-center gap-1 px-4 pt-3 shrink-0" role="tablist">
{LANGUAGE_TABS.map((lang) => (
<button
key={lang.id}
role="tab"
aria-selected={activeLanguage === lang.id}
onClick={() => {
setActiveLanguage(lang.id);
setCopied(false);
}}
className={`px-3 py-1.5 text-xs font-medium rounded transition-colors ${
activeLanguage === lang.id
? "bg-primary/10 text-primary"
: "text-text-muted hover:text-text-main hover:bg-black/5 dark:hover:bg-white/5"
}`}
>
{lang.label}
</button>
))}
</div>
{/* Code block */}
<div className="flex-1 overflow-y-auto px-4 pt-3 pb-4 min-h-0">
{hasRealKey ? (
<div className="text-xs text-destructive bg-destructive/10 rounded p-3">
{t("exportRealKeyWarning")}
</div>
) : (
<pre className="text-xs font-mono text-text-main bg-bg-alt border border-border rounded-lg p-4 overflow-x-auto whitespace-pre-wrap break-all">
<code>{currentCode}</code>
</pre>
)}
{/* Placeholder hint */}
<p className="text-[11px] text-text-muted mt-2">
{t("placeholderHintPrefix")}{" "}
<code className="font-mono text-primary">{API_KEY_PLACEHOLDER}</code>
{" "}{t("placeholderHintSuffix")}
</p>
</div>
{/* Footer with copy button */}
<div className="flex items-center justify-end gap-3 px-5 py-3 border-t border-border shrink-0">
<button
onClick={onClose}
className="text-xs px-3 py-1.5 rounded border border-border text-text-muted hover:text-text-main hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
>
{t("close")}
</button>
<button
onClick={handleCopy}
disabled={hasRealKey}
className={`flex items-center gap-1.5 text-xs px-3 py-1.5 rounded border transition-colors ${
copied
? "border-green-500 text-green-500 bg-green-500/10"
: "border-primary text-primary hover:bg-primary/10"
} disabled:opacity-40 disabled:cursor-not-allowed`}
aria-label={t("copyLangCode", { language: activeLanguage })}
>
<span className="material-symbols-outlined text-[14px]">
{copied ? "check" : "content_copy"}
</span>
{copied ? t("copiedCode") : t("copy")}
</button>
</div>
</div>
</div>
);
}
|