117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Copy, Check } from "lucide-react";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
} from "@/components/ui/dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { toast } from "sonner";
|
|
|
|
const API_BASE =
|
|
process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3000/api/v1";
|
|
|
|
interface IptvExportDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
token: string;
|
|
}
|
|
|
|
export function IptvExportDialog({
|
|
open,
|
|
onOpenChange,
|
|
token,
|
|
}: IptvExportDialogProps) {
|
|
const m3uUrl = `${API_BASE}/iptv/playlist.m3u?token=${token}`;
|
|
const xmltvUrl = `${API_BASE}/iptv/epg.xml?token=${token}`;
|
|
|
|
const [copiedM3u, setCopiedM3u] = useState(false);
|
|
const [copiedXmltv, setCopiedXmltv] = useState(false);
|
|
|
|
const copy = async (text: string, which: "m3u" | "xmltv") => {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
if (which === "m3u") {
|
|
setCopiedM3u(true);
|
|
setTimeout(() => setCopiedM3u(false), 2000);
|
|
} else {
|
|
setCopiedXmltv(true);
|
|
setTimeout(() => setCopiedXmltv(false), 2000);
|
|
}
|
|
toast.success("Copied to clipboard");
|
|
} catch {
|
|
toast.error("Failed to copy");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="bg-zinc-900 border-zinc-800 text-zinc-100 sm:max-w-lg">
|
|
<DialogHeader>
|
|
<DialogTitle>IPTV Export</DialogTitle>
|
|
<DialogDescription className="text-zinc-400">
|
|
Paste these URLs into your IPTV client (TiviMate, VLC, etc.).
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-zinc-300 text-xs">M3U Playlist</Label>
|
|
<p className="text-xs text-zinc-500">
|
|
Add Playlist → paste URL
|
|
</p>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
readOnly
|
|
value={m3uUrl}
|
|
className="bg-zinc-800 border-zinc-700 text-zinc-300 font-mono text-xs"
|
|
/>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="shrink-0 border-zinc-700 text-zinc-400 hover:text-zinc-100"
|
|
onClick={() => copy(m3uUrl, "m3u")}
|
|
>
|
|
{copiedM3u ? <Check className="size-4" /> : <Copy className="size-4" />}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<Label className="text-zinc-300 text-xs">XMLTV EPG</Label>
|
|
<p className="text-xs text-zinc-500">
|
|
EPG Source → paste URL
|
|
</p>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
readOnly
|
|
value={xmltvUrl}
|
|
className="bg-zinc-800 border-zinc-700 text-zinc-300 font-mono text-xs"
|
|
/>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
className="shrink-0 border-zinc-700 text-zinc-400 hover:text-zinc-100"
|
|
onClick={() => copy(xmltvUrl, "xmltv")}
|
|
>
|
|
{copiedXmltv ? <Check className="size-4" /> : <Copy className="size-4" />}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-xs text-zinc-600">
|
|
The token in these URLs is your session JWT. Anyone with these URLs
|
|
can stream your channels.
|
|
</p>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|