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">&lt;/&gt;</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>
  );
}