ERP-node/frontend/components/v2/config-panels/V2WebViewConfigPanel.tsx

237 lines
10 KiB
TypeScript
Raw Normal View History

"use client";
import React, { useState } from "react";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Globe, Shield, Settings, ChevronDown, Info, Copy, Check } from "lucide-react";
import { cn } from "@/lib/utils";
import type { V2WebViewConfig } from "@/lib/registry/components/v2-web-view/types";
interface V2WebViewConfigPanelProps {
config: V2WebViewConfig;
onChange: (config: Partial<V2WebViewConfig>) => void;
}
const SSO_GUIDE_SNIPPET = `// URL에서 sso_token 파라미터를 읽어 JWT를 디코딩하세요
const token = url.searchParams.get("sso_token");
const payload = JSON.parse(atob(token.split(".")[1]));
// payload.userId, payload.userName, payload.companyCode`;
export const V2WebViewConfigPanel: React.FC<V2WebViewConfigPanelProps> = ({ config, onChange }) => {
const [advancedOpen, setAdvancedOpen] = useState(false);
const [guideOpen, setGuideOpen] = useState(false);
const [copied, setCopied] = useState(false);
const handleCopySnippet = () => {
navigator.clipboard.writeText(SSO_GUIDE_SNIPPET).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
};
const updateConfig = (field: keyof V2WebViewConfig, value: any) => {
const newConfig = { ...config, [field]: value };
onChange({ [field]: value });
if (typeof window !== "undefined") {
window.dispatchEvent(
new CustomEvent("componentConfigChanged", {
detail: { config: newConfig },
}),
);
}
};
return (
<div className="space-y-4">
{/* ─── 1단계: URL 입력 ─── */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<Globe className="text-primary h-4 w-4" />
<p className="text-sm font-medium"> URL</p>
</div>
<Input
value={config.url || ""}
onChange={(e) => updateConfig("url", e.target.value)}
placeholder="https://example.com"
className="h-8 text-sm"
/>
<p className="text-muted-foreground text-[11px]"> </p>
</div>
{/* ─── 2단계: SSO 연동 ─── */}
<div className="space-y-2">
<div className="flex items-center justify-between py-1">
<div className="flex items-center gap-2">
<Shield className="text-muted-foreground h-4 w-4" />
<div>
<p className="text-sm">SSO </p>
<p className="text-muted-foreground text-[11px]"> URL에 </p>
</div>
</div>
<Switch checked={config.useSSO || false} onCheckedChange={(checked) => updateConfig("useSSO", checked)} />
</div>
{config.useSSO && (
<Collapsible open={guideOpen} onOpenChange={setGuideOpen}>
<CollapsibleTrigger asChild>
<button
type="button"
className="flex w-full items-center gap-2 rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-left transition-colors hover:bg-blue-100 dark:border-blue-900 dark:bg-blue-950 dark:hover:bg-blue-900"
>
<Info className="h-3.5 w-3.5 shrink-0 text-blue-500" />
<span className="text-[11px] font-medium text-blue-700 dark:text-blue-300"> </span>
<ChevronDown
className={cn(
"ml-auto h-3.5 w-3.5 text-blue-400 transition-transform duration-200",
guideOpen && "rotate-180",
)}
/>
</button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="space-y-2.5 rounded-b-lg border border-t-0 border-blue-200 bg-blue-50/50 px-3 py-3 dark:border-blue-900 dark:bg-blue-950/50">
<div className="space-y-1">
<p className="text-[11px] font-medium text-blue-800 dark:text-blue-200"> </p>
<p className="text-muted-foreground text-[10px]">URL JWT가 .</p>
<code className="bg-muted/80 block rounded px-2 py-1 text-[10px]">?sso_token=eyJhbGciOi...</code>
</div>
<div className="space-y-1">
<p className="text-[11px] font-medium text-blue-800 dark:text-blue-200">JWT Payload </p>
<div className="bg-muted/80 rounded px-2 py-1.5 text-[10px] leading-relaxed">
<div className="flex justify-between">
<span className="text-muted-foreground">userId</span>
<span> ID</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">userName</span>
<span> </span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">companyCode</span>
<span> </span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">role</span>
<span></span>
</div>
</div>
</div>
<div className="space-y-1">
<div className="flex items-center justify-between">
<p className="text-[11px] font-medium text-blue-800 dark:text-blue-200"> </p>
<button
type="button"
onClick={handleCopySnippet}
className="flex items-center gap-1 text-[10px] text-blue-500 hover:text-blue-700"
>
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
{copied ? "복사됨" : "복사"}
</button>
</div>
<pre className="bg-muted/80 overflow-x-auto rounded px-2 py-1.5 text-[10px] leading-relaxed whitespace-pre-wrap">
{`const token = url.searchParams
.get("sso_token");
const payload = JSON.parse(
atob(token.split(".")[1])
);
// payload.userId
// payload.companyCode`}
</pre>
</div>
</div>
</CollapsibleContent>
</Collapsible>
)}
</div>
{/* ─── 3단계: 표시 옵션 ─── */}
<div className="space-y-2">
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-muted-foreground text-[11px]"> </p>
</div>
<Switch
checked={config.showBorder !== false}
onCheckedChange={(checked) => updateConfig("showBorder", checked)}
/>
</div>
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-muted-foreground text-[11px]"> </p>
</div>
<Switch
checked={config.allowFullscreen || false}
onCheckedChange={(checked) => updateConfig("allowFullscreen", checked)}
/>
</div>
</div>
{/* ─── 4단계: 고급 설정 (기본 접혀있음) ─── */}
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<CollapsibleTrigger asChild>
<button
type="button"
className="bg-muted/30 hover:bg-muted/50 flex w-full items-center justify-between rounded-lg border px-4 py-2.5 text-left transition-colors"
>
<div className="flex items-center gap-2">
<Settings className="text-muted-foreground h-4 w-4" />
<span className="text-sm font-medium"> </span>
</div>
<ChevronDown
className={cn(
"text-muted-foreground h-4 w-4 transition-transform duration-200",
advancedOpen && "rotate-180",
)}
/>
</button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="space-y-3 rounded-b-lg border border-t-0 p-4">
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-muted-foreground text-[11px]"> iframe </p>
</div>
<Switch
checked={config.sandbox !== false}
onCheckedChange={(checked) => updateConfig("sandbox", checked)}
/>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-muted-foreground text-xs"> </span>
<Input
value={config.borderRadius || "8px"}
onChange={(e) => updateConfig("borderRadius", e.target.value)}
placeholder="8px"
className="h-7 w-[100px] text-xs"
/>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-muted-foreground text-xs"> </span>
<Input
value={config.loadingText || ""}
onChange={(e) => updateConfig("loadingText", e.target.value)}
placeholder="로딩 중..."
className="h-7 w-[140px] text-xs"
/>
</div>
</div>
</CollapsibleContent>
</Collapsible>
</div>
);
};
V2WebViewConfigPanel.displayName = "V2WebViewConfigPanel";
export default V2WebViewConfigPanel;