Files
tgex-frontend/app/(dashboard)/creatives/new/page.tsx
2025-11-19 12:56:04 +03:00

250 lines
8.2 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";
// ============================================================================
// 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="🔥 Присоединяйся к нашему сообществу!&#10;&#10;Узнавай первым о новых возможностях.&#10;&#10;👉 {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>
</>
);
}