package screens
import (
"context"
"fmt"
"strings"
"example.com/m/adapter/backend"
"example.com/m/bot"
"github.com/NicoNex/echotron/v3"
"github.com/rs/zerolog/log"
)
type CreativeDetails struct {
CreativeEditorFields // Встраивание общих полей
CreativeID string
ProjectID string
WorkspaceID string
BackState bot.State
// Флаг подтверждения удаления
confirmingDelete bool
}
func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
// Получаем JWT
jwt, err := s.ensureJWT(b)
if err != nil {
log.Error().Err(err).Msg("Failed to get JWT")
b.SendNew("❌ Ошибка авторизации\n\nПопробуйте /start", bot.Keyboard())
return
}
// Загружаем креатив
creative, err := b.Backend.GetCreative(context.Background(), jwt, s.WorkspaceID, s.CreativeID)
if err != nil {
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to get creative")
b.SendNew("❌ Не удалось загрузить креатив", bot.Keyboard(
bot.Row(bot.Button("« Назад", "back")),
))
return
}
// Заполняем поля из загруженного креатива
s.Name = &creative.Name
if creative.Text != "" {
s.Text = &creative.Text
log.Info().Str("creative_text", creative.Text).Msg("Set creative text from backend")
} else {
log.Info().Msg("Creative text is empty from backend")
}
// Buttons, MediaType, MediaFileID остаются пустыми (не сохраняются в БД)
// Устанавливаем навигацию
s.Navigation = "… › Креативы › Детали"
log.Info().
Str("creative_id", s.CreativeID).
Str("name", creative.Name).
Bool("has_text", creative.Text != "").
Bool("s_text_is_nil", s.Text == nil).
Msg("Sending creative preview and panel")
// Отправляем превью и панель управления
s.SendCreativePreview(b)
s.showManagementPanelWithDelete(b)
}
func (s *CreativeDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
if u.CallbackQuery == nil || u.CallbackQuery.Data == "" {
return
}
data := u.CallbackQuery.Data
// Обрабатываем общие callbacks (edit_text, add_button, add_media, delete_button, delete_media)
switch {
case data == "cancel", data == "back":
// Отмена - возвращаемся назад
if s.BackState != nil {
b.SetState(s.BackState, bot.EditMessage)
}
case data == "edit_text":
s.ShowControlPanel(b, `✎ Введите новый текст креатива:
⚠ Важно: Текст должен содержать ОДНУ инвайт-ссылку вашего канала
Формат ссылки:
https://t.me/+xxx
Пример:
Присоединяйтесь к нашему каналу!
https://t.me/+AbCdEfGhIjKlMn`, [][]echotron.InlineKeyboardButton{
bot.Row(bot.Button("« Отмена редактирования", "cancel_edit")),
})
case data == "add_button":
s.WaitingForButtonText = true
s.ShowControlPanel(b, `+ Добавить кнопку
Введите текст кнопки:
Например:
• Перейти на сайт
• Написать нам`, [][]echotron.InlineKeyboardButton{
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
})
case data == "add_media":
s.WaitingForMedia = true
s.ShowControlPanel(b, `⧉ Добавить медиа
Отправьте фото, видео или GIF
Это медиа будет отображаться в креативе вместе с текстом`, [][]echotron.InlineKeyboardButton{
bot.Row(bot.Button("« Отмена", "cancel_media")),
})
case data == "delete_media":
s.MediaType = ""
s.MediaFileID = ""
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
case data == "cancel_add_button":
// Отменяем добавление кнопки
if s.WaitingForButtonURL && len(s.Buttons) > 0 {
lastButton := s.Buttons[len(s.Buttons)-1]
if lastButton.URL == "https://example.com" {
s.Buttons = s.Buttons[:len(s.Buttons)-1]
s.UpdateCreativePreview(b)
}
}
s.WaitingForButtonText = false
s.WaitingForButtonURL = false
s.CurrentButtonText = nil
s.showManagementPanelWithDelete(b)
case data == "cancel_edit", data == "cancel_media":
s.WaitingForMedia = false
s.showManagementPanelWithDelete(b)
case data == "confirm_save":
s.updateCreative(b)
case data == "delete_creative":
if !s.confirmingDelete {
// Первое нажатие - просим подтверждение
s.confirmingDelete = true
s.showManagementPanelWithDelete(b)
} else {
// Второе нажатие - удаляем
s.deleteCreative(b)
}
case data == "confirm_delete":
// Это только для безопасности, главная логика в delete_creative
s.deleteCreative(b)
case strings.HasPrefix(data, "delete_button:"):
parts := strings.Split(data, ":")
if len(parts) == 2 {
var index int
if _, err := fmt.Sscanf(parts[1], "%d", &index); err == nil {
if index >= 0 && index < len(s.Buttons) {
s.Buttons = append(s.Buttons[:index], s.Buttons[index+1:]...)
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
}
}
}
default:
// Сбрасываем флаг подтверждения удаления при любом другом действии
if s.confirmingDelete {
s.confirmingDelete = false
s.showManagementPanelWithDelete(b)
}
}
}
func (s *CreativeDetails) HandleMessage(b *bot.Bot, u *echotron.Update) {
if u.Message == nil {
return
}
// Обрабатываем добавление медиа
if s.WaitingForMedia {
if u.Message.Photo != nil && len(u.Message.Photo) > 0 {
photos := u.Message.Photo
largestPhoto := photos[len(photos)-1]
s.MediaType = "photo"
s.MediaFileID = largestPhoto.FileID
s.WaitingForMedia = false
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
return
} else if u.Message.Video != nil {
s.MediaType = "video"
s.MediaFileID = u.Message.Video.FileID
s.WaitingForMedia = false
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
return
} else if u.Message.Animation != nil {
s.MediaType = "animation"
s.MediaFileID = u.Message.Animation.FileID
s.WaitingForMedia = false
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
return
}
}
// Обрабатываем ввод текста кнопки
if s.WaitingForButtonText && u.Message.Text != "" {
buttonText := u.Message.Text
s.CurrentButtonText = &buttonText
s.WaitingForButtonText = false
s.WaitingForButtonURL = true
// Создаём кнопку с placeholder URL
s.Buttons = append(s.Buttons, InlineButton{
Text: buttonText,
URL: "https://example.com",
})
s.UpdateCreativePreview(b)
s.ShowControlPanel(b, `✧ Введите URL для кнопки:
Например:
https://example.com`, [][]echotron.InlineKeyboardButton{
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
})
return
}
// Обрабатываем ввод URL кнопки
if s.WaitingForButtonURL && u.Message.Text != "" {
url := strings.TrimSpace(u.Message.Text)
// Простая валидация URL
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
s.ShowControlPanel(b, `❌ Неверный формат URL
URL должен начинаться с http:// или https://
Попробуйте ещё раз:`, [][]echotron.InlineKeyboardButton{
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
})
return
}
// Обновляем URL последней кнопки
if len(s.Buttons) > 0 {
s.Buttons[len(s.Buttons)-1].URL = url
}
s.WaitingForButtonURL = false
s.CurrentButtonText = nil
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
return
}
// Обрабатываем редактирование текста креатива
if !s.WaitingForButtonText && !s.WaitingForButtonURL && !s.WaitingForMedia && u.Message.Text != "" {
text := s.GetFormattedText(u.Message)
s.Text = &text
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
return
}
// Обрабатываем имя креатива (если нет других состояний)
if u.Message.Text != "" {
text := s.GetFormattedText(u.Message)
s.Text = &text
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
}
}
func (s *CreativeDetails) Handle(_ *bot.Bot, _ *echotron.Update) { return }
func (s *CreativeDetails) showManagementPanelWithDelete(b *bot.Bot) {
var confirmText, confirmCallback string
if s.confirmingDelete {
confirmText = "✖ Подтвердить удаление"
confirmCallback = "confirm_delete"
} else {
confirmText = "✓ Сохранить"
confirmCallback = "confirm_save"
}
// Добавляем кнопку удаления креатива
deleteButton := bot.Row(bot.Button("⌦ Удалить креатив", "delete_creative"))
s.ShowManagementPanel(b, confirmText, confirmCallback, "cancel", deleteButton)
}
func (s *CreativeDetails) updateCreative(b *bot.Bot) {
// Получаем JWT
jwt, err := s.ensureJWT(b)
if err != nil {
log.Error().Err(err).Msg("Failed to get JWT")
b.SendNew("❌ Ошибка авторизации\n\nПопробуйте /start", bot.Keyboard())
return
}
// Подготавливаем данные для обновления
input := backend.UpdateCreativeInput{
Name: s.Name,
Text: s.Text,
}
// Отправляем запрос на обновление
_, err = b.Backend.UpdateCreative(
context.Background(),
jwt,
s.WorkspaceID,
s.CreativeID,
input,
)
if err != nil {
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to update creative")
b.SendNew("❌ Не удалось сохранить изменения", bot.Keyboard(
bot.Row(bot.Button("« Назад", "back")),
))
return
}
log.Info().Str("creative_id", s.CreativeID).Msg("Creative updated successfully")
// Возвращаемся назад
if s.BackState != nil {
b.SendNew("✅ Креатив успешно обновлен!", bot.Keyboard())
b.SetState(s.BackState, bot.NewMessage)
}
}
func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
// Получаем JWT
jwt, err := s.ensureJWT(b)
if err != nil {
log.Error().Err(err).Msg("Failed to get JWT")
b.SendNew("❌ Ошибка авторизации\n\nПопробуйте /start", bot.Keyboard())
return
}
// Отправляем запрос на удаление
err = b.Backend.DeleteCreative(
context.Background(),
jwt,
s.WorkspaceID,
s.CreativeID,
)
if err != nil {
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to delete creative")
b.SendNew("❌ Не удалось удалить креатив", bot.Keyboard(
bot.Row(bot.Button("« Назад", "back")),
))
return
}
log.Info().Str("creative_id", s.CreativeID).Msg("Creative deleted successfully")
// Возвращаемся назад
if s.BackState != nil {
b.SendNew("✅ Креатив успешно удален!", bot.Keyboard())
b.SetState(s.BackState, bot.NewMessage)
}
}
func (s *CreativeDetails) ensureJWT(b *bot.Bot) (string, error) {
// Проверяем есть ли JWT в сессии
if b.Session.JWT != "" {
return b.Session.JWT, nil
}
// Если нет, запрашиваем новый
jwt, err := b.Backend.GetJWTByTelegramID(context.Background(), b.ChatID)
if err != nil {
return "", err
}
// Сохраняем в сессию
b.Session.JWT = jwt
return jwt, nil
}