278 lines
10 KiB
TypeScript
278 lines
10 KiB
TypeScript
import { Button } from "@xtablo/ui/components/button";
|
|
import { CopyButton } from "@xtablo/ui/components/clipboard";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@xtablo/ui/components/dialog";
|
|
import { Label } from "@xtablo/ui/components/label";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@xtablo/ui/components/select";
|
|
import { TypographyMuted, TypographyP } from "@xtablo/ui/components/typography";
|
|
import { useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
type ColorVariant = "black" | "white" | "blue" | "purple" | "green" | "orange" | "red";
|
|
|
|
export type EmbedType = "embed" | "floating" | "normal";
|
|
|
|
interface EmbedConfig {
|
|
embedType: EmbedType;
|
|
backgroundVariant: ColorVariant;
|
|
buttonVariant: ColorVariant;
|
|
}
|
|
|
|
interface EmbedConfigModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
buildPublicLink: (type: "embed" | "floating" | "normal") => string;
|
|
}
|
|
|
|
export function EmbedConfigModal({ isOpen, onClose, buildPublicLink }: EmbedConfigModalProps) {
|
|
const { t } = useTranslation("components");
|
|
const [embedConfig, setEmbedConfig] = useState<EmbedConfig>({
|
|
embedType: "embed",
|
|
backgroundVariant: "purple",
|
|
buttonVariant: "purple",
|
|
});
|
|
|
|
const getEmbedUrl = () => {
|
|
const baseUrl = buildPublicLink(embedConfig.embedType);
|
|
const params = new URLSearchParams({
|
|
mode: "embed",
|
|
buttonVariant: embedConfig.buttonVariant,
|
|
});
|
|
|
|
// Only add backgroundVariant for full embed
|
|
if (embedConfig.embedType === "embed") {
|
|
params.set("backgroundVariant", embedConfig.backgroundVariant);
|
|
}
|
|
|
|
return `${baseUrl}?${params.toString()}`;
|
|
};
|
|
|
|
const generateEmbedCode = () => {
|
|
const embedUrl = getEmbedUrl();
|
|
|
|
if (embedConfig.embedType === "floating") {
|
|
return `<!-- Xtablo Floating Widget -->
|
|
<div id="xtablo-widget-root"></div>
|
|
<script>
|
|
(function() {
|
|
var root = document.getElementById('xtablo-widget-root');
|
|
var isOpen = false;
|
|
var modal = null;
|
|
|
|
// Create floating button
|
|
var button = document.createElement('div');
|
|
button.style.cssText = 'position: fixed; bottom: 24px; right: 24px; z-index: 999998; cursor: pointer; width: 60px; height: 60px;';
|
|
button.innerHTML = '<iframe src="${embedUrl}&view=button" style="width: 100%; height: 100%; border: none; border-radius: 50%;" frameborder="0" scrolling="no"></iframe>';
|
|
|
|
// Create modal container (hidden by default)
|
|
modal = document.createElement('div');
|
|
modal.style.cssText = 'position: fixed; bottom: 0; right: 0; width: 100%; height: 100%; z-index: 999999; display: none; pointer-events: none;';
|
|
|
|
var modalIframe = document.createElement('iframe');
|
|
modalIframe.src = '${embedUrl}&view=modal';
|
|
modalIframe.style.cssText = 'position: absolute; bottom: 0; right: 0; width: 100%; height: 100%; border: none; background: transparent; pointer-events: auto;';
|
|
modalIframe.setAttribute('frameborder', '0');
|
|
modalIframe.setAttribute('allow', 'clipboard-write');
|
|
|
|
modal.appendChild(modalIframe);
|
|
|
|
// Toggle modal on button click
|
|
button.addEventListener('click', function() {
|
|
isOpen = !isOpen;
|
|
modal.style.display = isOpen ? 'block' : 'none';
|
|
});
|
|
|
|
// Listen for close messages from iframe
|
|
window.addEventListener('message', function(event) {
|
|
if (event.data === 'xtablo:close') {
|
|
isOpen = false;
|
|
modal.style.display = 'none';
|
|
} else if (event.data === 'xtablo:open') {
|
|
isOpen = true;
|
|
modal.style.display = 'block';
|
|
}
|
|
});
|
|
|
|
root.appendChild(button);
|
|
root.appendChild(modal);
|
|
})();
|
|
</script>`;
|
|
}
|
|
|
|
return `<iframe
|
|
src="${embedUrl}"
|
|
width="1130"
|
|
height="700"
|
|
frameborder="0"
|
|
style="border: none; border-radius: 8px;"
|
|
></iframe>`;
|
|
};
|
|
|
|
const colorOptions: { value: ColorVariant; label: string; color: string }[] = [
|
|
{ value: "black", label: t("embedConfigModal.colors.black"), color: "bg-gray-900" },
|
|
{ value: "white", label: t("embedConfigModal.colors.white"), color: "bg-white" },
|
|
{ value: "blue", label: t("embedConfigModal.colors.blue"), color: "bg-blue-600" },
|
|
{ value: "purple", label: t("embedConfigModal.colors.purple"), color: "bg-purple-600" },
|
|
{ value: "green", label: t("embedConfigModal.colors.green"), color: "bg-green-600" },
|
|
{ value: "orange", label: t("embedConfigModal.colors.orange"), color: "bg-orange-600" },
|
|
{ value: "red", label: t("embedConfigModal.colors.red"), color: "bg-red-600" },
|
|
];
|
|
|
|
return (
|
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>{t("embedConfigModal.title")}</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-6 overflow-hidden">
|
|
{/* Configuration Section */}
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label>{t("embedConfigModal.labels.integrationType")}</Label>
|
|
<Select
|
|
value={embedConfig.embedType}
|
|
onValueChange={(value) =>
|
|
setEmbedConfig({ ...embedConfig, embedType: value as EmbedType })
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="embed">
|
|
<div className="flex flex-col items-start">
|
|
<TypographyP className="font-medium">
|
|
{t("embedConfigModal.types.fullPage")}
|
|
</TypographyP>
|
|
</div>
|
|
</SelectItem>
|
|
<SelectItem value="floating">
|
|
<div className="flex flex-col items-start">
|
|
<TypographyP className="font-medium">
|
|
{t("embedConfigModal.types.floatingWidget")}
|
|
</TypographyP>
|
|
</div>
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{embedConfig.embedType === "embed" && (
|
|
<div className="space-y-2">
|
|
<Label>{t("embedConfigModal.labels.backgroundColor")}</Label>
|
|
<Select
|
|
value={embedConfig.backgroundVariant}
|
|
onValueChange={(value) =>
|
|
setEmbedConfig({ ...embedConfig, backgroundVariant: value as ColorVariant })
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{colorOptions.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
<div className="flex items-center gap-2">
|
|
<div className={`w-4 h-4 rounded ${option.color}`}></div>
|
|
<span>{option.label}</span>
|
|
</div>
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-2">
|
|
<Label>{t("embedConfigModal.labels.buttonColor")}</Label>
|
|
<Select
|
|
value={embedConfig.buttonVariant}
|
|
onValueChange={(value) =>
|
|
setEmbedConfig({ ...embedConfig, buttonVariant: value as ColorVariant })
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{colorOptions.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
<div className="flex items-center gap-2">
|
|
<div className={`w-4 h-4 rounded ${option.color}`}></div>
|
|
<span>{option.label}</span>
|
|
</div>
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Preview Link */}
|
|
<div className="space-y-2 pt-4 border-t">
|
|
<Label>{t("embedConfigModal.labels.previewLink")}</Label>
|
|
<div className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
readOnly
|
|
value={getEmbedUrl()}
|
|
className="flex-1 min-w-0 px-3 py-2 text-sm bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md overflow-hidden text-ellipsis"
|
|
/>
|
|
<Button
|
|
variant="outline"
|
|
className="shrink-0"
|
|
onClick={() => window.open(getEmbedUrl(), "_blank")}
|
|
>
|
|
{t("embedConfigModal.buttons.preview")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Embed Code */}
|
|
<div className="space-y-2 min-w-0">
|
|
<Label>{t("embedConfigModal.labels.embedCode")}</Label>
|
|
<TypographyMuted className="text-xs">
|
|
{embedConfig.embedType === "floating"
|
|
? t("embedConfigModal.descriptions.floatingWidget")
|
|
: t("embedConfigModal.descriptions.fullPage")}
|
|
</TypographyMuted>
|
|
<div className="relative min-w-0">
|
|
<div className="overflow-auto max-w-full">
|
|
<pre className="p-4 pr-16 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md text-xs whitespace-pre-wrap wrap-break-word w-full">
|
|
<code className="break-all">{generateEmbedCode()}</code>
|
|
</pre>
|
|
</div>
|
|
<div className="absolute top-2 right-2">
|
|
<CopyButton
|
|
copyValue={generateEmbedCode()}
|
|
label={t("embedConfigModal.buttons.copy")}
|
|
labelAfterCopied={t("embedConfigModal.buttons.copied")}
|
|
variant="outline"
|
|
size="sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={onClose}>
|
|
{t("embedConfigModal.buttons.close")}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|