250 lines
8.2 KiB
TypeScript
250 lines
8.2 KiB
TypeScript
"use client";
|
||
|
||
// ============================================================================
|
||
// Create Creative Page
|
||
// ============================================================================
|
||
|
||
import React, { useEffect, useState } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import { DashboardHeader } from "@/components/layout/dashboard-header";
|
||
import {
|
||
Card,
|
||
CardContent,
|
||
CardDescription,
|
||
CardHeader,
|
||
CardTitle,
|
||
} from "@/components/ui/card";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Textarea } from "@/components/ui/textarea";
|
||
import { Label } from "@/components/ui/label";
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from "@/components/ui/select";
|
||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||
import { creativesApi, channelsApi } from "@/lib/api";
|
||
import { AlertCircle, Loader2, ArrowLeft, Info } from "lucide-react";
|
||
import type { TargetChannel } from "@/lib/types/api";
|
||
|
||
export default function CreateCreativePage() {
|
||
const router = useRouter();
|
||
const [channels, setChannels] = useState<TargetChannel[]>([]);
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
const [formData, setFormData] = useState({
|
||
name: "",
|
||
text: "",
|
||
target_channel_id: "",
|
||
});
|
||
|
||
useEffect(() => {
|
||
loadChannels();
|
||
}, []);
|
||
|
||
const loadChannels = async () => {
|
||
try {
|
||
const response = await channelsApi.list();
|
||
setChannels(response.target_channels.filter((c) => c.is_active));
|
||
} catch (err) {
|
||
console.error("Error loading channels:", err);
|
||
}
|
||
};
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
setError(null);
|
||
|
||
// Validation
|
||
if (!formData.name.trim()) {
|
||
setError("Введите название креатива");
|
||
return;
|
||
}
|
||
|
||
if (!formData.text.trim()) {
|
||
setError("Введите текст креатива");
|
||
return;
|
||
}
|
||
|
||
if (!formData.text.includes("{link}")) {
|
||
setError("Текст должен содержать переменную {link} для вставки ссылки");
|
||
return;
|
||
}
|
||
|
||
if (!formData.target_channel_id) {
|
||
setError("Выберите целевой канал");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setIsLoading(true);
|
||
await creativesApi.create(formData);
|
||
router.push("/creatives");
|
||
} catch (err: any) {
|
||
setError(err?.error?.message || "Ошибка создания креатива");
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
const previewText = formData.text.replace(
|
||
"{link}",
|
||
"https://t.me/+InviteLinkExample"
|
||
);
|
||
|
||
return (
|
||
<>
|
||
<DashboardHeader
|
||
breadcrumbs={[
|
||
{ label: "Главная", href: "/" },
|
||
{ label: "Креативы", href: "/creatives" },
|
||
{ label: "Создание" },
|
||
]}
|
||
/>
|
||
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4 max-w-4xl mx-auto w-full">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-3xl font-bold tracking-tight">
|
||
Создать креатив
|
||
</h1>
|
||
<p className="text-muted-foreground mt-1">
|
||
Новый шаблон рекламного сообщения
|
||
</p>
|
||
</div>
|
||
<Button asChild variant="outline">
|
||
<a href="/creatives">
|
||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||
Назад
|
||
</a>
|
||
</Button>
|
||
</div>
|
||
|
||
{error && (
|
||
<Alert variant="destructive">
|
||
<AlertCircle className="h-4 w-4" />
|
||
<AlertDescription>{error}</AlertDescription>
|
||
</Alert>
|
||
)}
|
||
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Основная информация</CardTitle>
|
||
<CardDescription>
|
||
Название и целевой канал креатива
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="name">Название креатива *</Label>
|
||
<Input
|
||
id="name"
|
||
value={formData.name}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, name: e.target.value })
|
||
}
|
||
placeholder='Например: Креатив "Присоединяйся"'
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="target_channel_id">Целевой канал *</Label>
|
||
<Select
|
||
value={formData.target_channel_id}
|
||
onValueChange={(value) =>
|
||
setFormData({ ...formData, target_channel_id: value })
|
||
}
|
||
>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="Выберите канал" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{channels.map((channel) => (
|
||
<SelectItem key={channel.id} value={channel.id}>
|
||
{channel.title}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Текст сообщения</CardTitle>
|
||
<CardDescription>
|
||
Используйте переменную {"{link}"} для вставки ссылки
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="text">Текст креатива *</Label>
|
||
<Textarea
|
||
id="text"
|
||
value={formData.text}
|
||
onChange={(e) =>
|
||
setFormData({ ...formData, text: e.target.value })
|
||
}
|
||
placeholder="🔥 Присоединяйся к нашему сообществу! Узнавай первым о новых возможностях. 👉 {link}"
|
||
rows={8}
|
||
className="font-mono text-sm"
|
||
/>
|
||
</div>
|
||
|
||
<Alert>
|
||
<Info className="h-4 w-4" />
|
||
<AlertDescription className="text-sm">
|
||
<strong>Обязательно</strong> включите переменную {"{link}"} в
|
||
текст. На её место будет подставлена пригласительная ссылка в
|
||
целевой канал.
|
||
</AlertDescription>
|
||
</Alert>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{formData.text && (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Предпросмотр</CardTitle>
|
||
<CardDescription>
|
||
Так будет выглядеть сообщение для размещения
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="rounded-lg border bg-muted/50 p-4">
|
||
<p className="text-sm whitespace-pre-wrap">{previewText}</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
<div className="flex justify-end gap-2">
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
onClick={() => router.push("/creatives")}
|
||
>
|
||
Отмена
|
||
</Button>
|
||
<Button type="submit" disabled={isLoading}>
|
||
{isLoading ? (
|
||
<>
|
||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||
Создание...
|
||
</>
|
||
) : (
|
||
"Создать креатив"
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</>
|
||
);
|
||
}
|