Files
tgex-frontend/components/dialog-add-channel.tsx
2026-01-21 22:43:54 +03:00

200 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { Loader2, Plus, AlertCircle, CheckCircle2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import { parseChannelInput } from "@/lib/utils/channel-parser";
import { channelsApi, placementsApi } from "@/lib/api";
import type { Placement } from "@/lib/types/api";
interface AddChannelDialogProps {
workspaceId: string;
projectId: string;
existingPlacements: Placement[];
onSuccess: () => void;
children?: React.ReactNode;
}
export function AddChannelDialog({
workspaceId,
projectId,
existingPlacements,
onSuccess,
children,
}: AddChannelDialogProps) {
const [open, setOpen] = useState(false);
const [inputValue, setInputValue] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const existingUsernames = new Set(
existingPlacements
.map((p) => p.channel.username?.toLowerCase())
.filter(Boolean)
);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setSuccess(false);
const username = parseChannelInput(inputValue);
if (!username) {
setError("Не удалось распознать username из ссылки");
return;
}
if (existingUsernames.has(username.toLowerCase())) {
setError("Этот канал уже добавлен в проект");
return;
}
try {
setLoading(true);
// Step 1: Verify/add channel via channels API
await channelsApi.list({ username });
// Step 2: Create empty placement
await placementsApi.createBulk(workspaceId, projectId, {
channels: [
{
username,
status: "Без статуса",
},
],
});
setSuccess(true);
setInputValue("");
// Close dialog after short delay
setTimeout(() => {
setOpen(false);
setSuccess(false);
onSuccess();
}, 1500);
} catch (err: any) {
console.error("Failed to add channel:", err);
setError(err.detail || "Не удалось добавить канал");
} finally {
setLoading(false);
}
};
const handleOpenChange = (isOpen: boolean) => {
if (!isOpen) {
setInputValue("");
setError(null);
setSuccess(false);
}
setOpen(isOpen);
};
const examples = [
"t.me/rian_ru",
"@rian_ru",
"telemetr.me/content/rian_ru",
"tgstat.ru/channel/@rian_ru",
];
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Добавить канал</DialogTitle>
<DialogDescription>
Введите ссылку или username канала для проверки и добавления в проект
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="channel-input">Ссылка или username канала</Label>
<Input
id="channel-input"
placeholder="https://t.me/rian_ru или @rian_ru"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
disabled={loading}
autoComplete="off"
/>
<p className="text-xs text-muted-foreground">
Поддерживаются: t.me, telemetr.me, tgstat.ru, а также username с @
</p>
</div>
{error && (
<div className="flex items-center gap-2 p-3 rounded-md bg-destructive/10 text-destructive text-sm">
<AlertCircle className="h-4 w-4 shrink-0" />
{error}
</div>
)}
{success && (
<div className="flex items-center gap-2 p-3 rounded-md bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400 text-sm">
<CheckCircle2 className="h-4 w-4 shrink-0" />
Канал успешно добавлен!
</div>
)}
<div className="space-y-2">
<p className="text-xs text-muted-foreground">Примеры:</p>
<div className="flex flex-wrap gap-2">
{examples.map((example) => (
<button
key={example}
type="button"
onClick={() => setInputValue(example)}
className="text-xs px-2 py-1 rounded-md bg-muted hover:bg-muted/80 transition-colors"
>
{example}
</button>
))}
</div>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setOpen(false)}
disabled={loading}
>
Отмена
</Button>
<Button type="submit" disabled={loading || !inputValue.trim()}>
{loading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Проверка...
</>
) : (
<>
<Plus className="h-4 w-4 mr-2" />
Проверить и добавить
</>
)}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}