Files
tgex-frontend/app/dashboard/[workspaceId]/creatives/new/page.tsx
2025-12-29 10:12:13 +03:00

243 lines
8.0 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 { useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { Loader2, ArrowLeft, Info } from "lucide-react";
import { DashboardHeader } from "@/components/layout/dashboard-header";
import { useWorkspace } from "@/components/providers/workspace-provider";
import { creativesApi } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
// ============================================================================
// Component
// ============================================================================
export default function NewCreativePage() {
const params = useParams();
const router = useRouter();
const workspaceId = params?.workspaceId as string;
const { currentProject } = useWorkspace();
const [name, setName] = useState("");
const [text, setText] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!name.trim() || !text.trim()) {
setError("Заполните все обязательные поля");
return;
}
if (!text.includes("{invite_link}")) {
setError("Текст должен содержать {invite_link} для вставки ссылки");
return;
}
if (!currentProject) {
setError("Выберите проект в верхнем меню");
return;
}
try {
setIsSubmitting(true);
setError(null);
const creative = await creativesApi.create(
workspaceId,
currentProject.id,
{
name: name.trim(),
text: text.trim(),
}
);
router.push(`/dashboard/${workspaceId}/creatives/${creative.id}`);
} catch (err: any) {
setError(err?.detail || "Не удалось создать креатив");
} finally {
setIsSubmitting(false);
}
};
const insertInviteLink = () => {
if (!text.includes("{invite_link}")) {
setText((prev) => prev + "{invite_link}");
}
};
return (
<>
<DashboardHeader
breadcrumbs={[
{ label: "Главная", href: `/dashboard/${workspaceId}` },
{ label: "Креативы", href: `/dashboard/${workspaceId}/creatives` },
{ label: "Новый креатив" },
]}
/>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4 max-w-2xl">
<div className="flex items-center gap-4">
<Button
variant="ghost"
size="icon"
onClick={() => router.back()}
>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<h1 className="text-2xl font-bold tracking-tight">Новый креатив</h1>
<p className="text-muted-foreground">
Создайте рекламный материал для размещений
</p>
</div>
</div>
{!currentProject && (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
Выберите проект в верхнем меню для создания креатива
</AlertDescription>
</Alert>
)}
<form onSubmit={handleSubmit}>
<Card>
<CardHeader>
<CardTitle>Информация о креативе</CardTitle>
<CardDescription>
Креатив это текст рекламного сообщения с пригласительной ссылкой
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">
Название <span className="text-destructive">*</span>
</Label>
<Input
id="name"
placeholder="Например: Летняя акция 2025"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={isSubmitting}
/>
<p className="text-xs text-muted-foreground">
Внутреннее название для удобства поиска
</p>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="text">
Текст креатива <span className="text-destructive">*</span>
</Label>
<Button
type="button"
variant="outline"
size="sm"
onClick={insertInviteLink}
disabled={text.includes("{invite_link}")}
>
Вставить {"{invite_link}"}
</Button>
</div>
<Textarea
id="text"
placeholder={`Пример:
🔥 Подпишись на наш канал!
Здесь ты найдёшь:
• Полезный контент
• Эксклюзивные материалы
• Актуальные новости
👉 {invite_link}`}
value={text}
onChange={(e) => setText(e.target.value)}
disabled={isSubmitting}
rows={10}
className="font-mono text-sm"
/>
<p className="text-xs text-muted-foreground">
Используйте <code className="bg-muted px-1 rounded">{"{invite_link}"}</code>{" "}
в том месте, где должна быть пригласительная ссылка
</p>
</div>
{text.includes("{invite_link}") && (
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
<span className="font-medium">Предпросмотр:</span>
<pre className="mt-2 whitespace-pre-wrap text-sm">
{text.replace(
"{invite_link}",
"https://t.me/+AbCdEfGhIjK"
)}
</pre>
</AlertDescription>
</Alert>
)}
{error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}
<div className="flex gap-2 pt-4">
<Button
type="button"
variant="outline"
onClick={() => router.back()}
disabled={isSubmitting}
>
Отмена
</Button>
<Button
type="submit"
disabled={
isSubmitting ||
!name.trim() ||
!text.trim() ||
!currentProject
}
>
{isSubmitting ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Создание...
</>
) : (
"Создать креатив"
)}
</Button>
</div>
</CardContent>
</Card>
</form>
</div>
</>
);
}