353 lines
10 KiB
Go
353 lines
10 KiB
Go
package screens
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"github.com/NicoNex/echotron/v3"
|
||
"github.com/TelegramExchange/tgex-backend/tg_bot/backend"
|
||
"github.com/TelegramExchange/tgex-backend/tg_bot/bot"
|
||
"github.com/TelegramExchange/tgex-backend/tg_bot/screens/ui"
|
||
"github.com/rs/zerolog/log"
|
||
)
|
||
|
||
type CreativeDetails struct {
|
||
CreativeEditorFields // Встраивание общих полей
|
||
|
||
CreativeID string
|
||
ProjectID string
|
||
BackState bot.State
|
||
|
||
// Флаг подтверждения удаления
|
||
confirmingDelete bool
|
||
}
|
||
|
||
func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||
creative, err := b.Backend.GetCreative(context.Background(), b.Session.JWT, b.Session.WorkspaceID, s.CreativeID)
|
||
if err != nil {
|
||
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to get creative")
|
||
b.SendNew("<b>❌ Не удалось загрузить креатив</b>", Keyboard(
|
||
Row(Button("← Назад", "back")),
|
||
))
|
||
return
|
||
}
|
||
|
||
// Заполняем поля из загруженного креатива
|
||
s.Name = &creative.Name
|
||
if creative.Text != "" {
|
||
sanitizedText := ui.SanitizeHTML(creative.Text)
|
||
s.Text = &sanitizedText
|
||
log.Info().Str("creative_text", creative.Text).Msg("Set creative text from backend")
|
||
} else {
|
||
log.Info().Msg("Creative text is empty from backend")
|
||
}
|
||
s.MediaType = creative.MediaType
|
||
s.MediaFileID = creative.MediaFileID
|
||
s.Buttons = nil
|
||
for _, btn := range creative.Buttons {
|
||
s.Buttons = append(s.Buttons, InlineButton{Text: btn.Text, URL: btn.URL})
|
||
}
|
||
s.MediaChanged = false
|
||
s.ClearInputMode()
|
||
|
||
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.InputMode = inputModeText
|
||
s.ShowTextEditPanel(b)
|
||
|
||
case data == "add_button":
|
||
s.InputMode = inputModeButtonText
|
||
s.PendingButtonType = "invite"
|
||
s.ShowAddButtonPanel(b)
|
||
|
||
case data == "add_media":
|
||
s.InputMode = inputModeMedia
|
||
s.ShowMediaPanel(b)
|
||
|
||
case data == "delete_media":
|
||
s.MediaType = ""
|
||
s.MediaFileID = ""
|
||
s.MediaChanged = true
|
||
s.UpdateCreativePreview(b)
|
||
s.showManagementPanelWithDelete(b)
|
||
|
||
case data == "cancel_add_button":
|
||
// Отменяем добавление кнопки
|
||
s.CancelPendingButton(b)
|
||
s.showManagementPanelWithDelete(b)
|
||
|
||
case data == "button_type_invite":
|
||
s.PendingButtonType = "invite"
|
||
s.InputMode = inputModeButtonText
|
||
s.ShowAddButtonPanel(b)
|
||
|
||
case data == "button_type_custom":
|
||
s.PendingButtonType = "custom"
|
||
s.InputMode = inputModeButtonText
|
||
s.ShowAddButtonPanel(b)
|
||
|
||
case data == "cancel_edit", data == "cancel_media":
|
||
s.ClearInputMode()
|
||
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
|
||
}
|
||
|
||
switch s.InputMode {
|
||
case inputModeMedia:
|
||
if !s.SetMediaFromMessage(u.Message) {
|
||
return
|
||
}
|
||
s.DeleteUserMessage(b, u.Message.ID)
|
||
s.UpdateCreativePreview(b)
|
||
s.ClearInputMode()
|
||
s.showManagementPanelWithDelete(b)
|
||
return
|
||
case inputModeButtonText:
|
||
if u.Message.Text == "" {
|
||
return
|
||
}
|
||
buttonText := u.Message.Text
|
||
s.DeleteUserMessage(b, u.Message.ID)
|
||
|
||
// Создаём кнопку с placeholder URL
|
||
if s.PendingButtonType == "custom" {
|
||
s.AddCustomButtonPlaceholder(buttonText)
|
||
s.UpdateCreativePreview(b)
|
||
s.InputMode = inputModeButtonURL
|
||
s.ShowButtonURLPanel(b)
|
||
} else {
|
||
s.AddInviteButton(buttonText)
|
||
s.UpdateCreativePreview(b)
|
||
s.PendingButtonType = ""
|
||
s.ClearInputMode()
|
||
s.showManagementPanelWithDelete(b)
|
||
}
|
||
return
|
||
case inputModeButtonURL:
|
||
if u.Message.Text == "" {
|
||
return
|
||
}
|
||
url := strings.TrimSpace(u.Message.Text)
|
||
s.DeleteUserMessage(b, u.Message.ID)
|
||
|
||
// Простая валидация URL
|
||
if !s.IsValidButtonURL(url, true) {
|
||
s.ShowInvalidButtonURLPanel(b)
|
||
return
|
||
}
|
||
|
||
// Обновляем URL последней кнопки
|
||
s.UpdateLastButtonURL(url)
|
||
|
||
s.ClearInputMode()
|
||
s.PendingButtonType = ""
|
||
|
||
s.UpdateCreativePreview(b)
|
||
s.showManagementPanelWithDelete(b)
|
||
return
|
||
case inputModeText:
|
||
if u.Message.Text == "" {
|
||
return
|
||
}
|
||
text := ui.FormatMessageHTML(u.Message)
|
||
s.Text = &text
|
||
|
||
s.DeleteUserMessage(b, u.Message.ID)
|
||
|
||
s.UpdateCreativePreview(b)
|
||
s.ClearInputMode()
|
||
s.showManagementPanelWithDelete(b)
|
||
return
|
||
}
|
||
|
||
// Обрабатываем имя креатива (если нет других состояний)
|
||
if u.Message.Text != "" {
|
||
text := ui.FormatMessageHTML(u.Message)
|
||
s.Text = &text
|
||
|
||
s.DeleteUserMessage(b, u.Message.ID)
|
||
|
||
s.UpdateCreativePreview(b)
|
||
s.showManagementPanelWithDelete(b)
|
||
}
|
||
}
|
||
|
||
func (s *CreativeDetails) Handle(_ *bot.Bot, _ *echotron.Update) { return }
|
||
|
||
func (s *CreativeDetails) Exit() {}
|
||
|
||
func (s *CreativeDetails) showManagementPanelWithDelete(b *bot.Bot) {
|
||
var confirmText, confirmCallback string
|
||
|
||
if s.confirmingDelete {
|
||
confirmText = "✖ Подтвердить удаление"
|
||
confirmCallback = "confirm_delete"
|
||
} else {
|
||
confirmText = "✓ Сохранить"
|
||
confirmCallback = "confirm_save"
|
||
}
|
||
|
||
// Добавляем кнопку удаления креатива
|
||
deleteButton := Row(Button("⌦ Удалить креатив", "delete_creative"))
|
||
|
||
s.ShowManagementPanel(b, confirmText, confirmCallback, "cancel", deleteButton)
|
||
}
|
||
|
||
func (s *CreativeDetails) updateCreative(b *bot.Bot) {
|
||
// JWT уже создан в Bot.Update(), просто берем из сессии
|
||
jwt := b.Session.JWT
|
||
if jwt == "" {
|
||
log.Error().Msg("JWT is empty in session")
|
||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", Keyboard())
|
||
return
|
||
}
|
||
|
||
var err error
|
||
|
||
// Подготавливаем данные для обновления
|
||
input := backend.UpdateCreativeInput{
|
||
Name: s.Name,
|
||
Text: s.Text,
|
||
}
|
||
if s.MediaChanged {
|
||
mediaType := s.MediaType
|
||
mediaFileID := s.MediaFileID
|
||
input.MediaType = &mediaType
|
||
input.MediaFileID = &mediaFileID
|
||
|
||
if s.MediaFileID != "" {
|
||
mediaData, err := b.DownloadFileBytes(s.MediaFileID)
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("Failed to download creative media")
|
||
// Показываем ошибку через панель управления
|
||
s.ShowControlPanel(b, "<b>❌ Не удалось загрузить медиа</b>\n\nПопробуйте удалить медиа и добавить заново, или нажмите \"Сохранить\" без изменения медиа.", [][]echotron.InlineKeyboardButton{
|
||
Row(Button("← Назад", "back")),
|
||
Row(Button("✓ Сохранить без медиа", "confirm_save")),
|
||
})
|
||
return
|
||
}
|
||
input.MediaData = &mediaData
|
||
} else {
|
||
emptyData := []byte{}
|
||
input.MediaData = &emptyData
|
||
}
|
||
}
|
||
|
||
buttons := make([]backend.CreativeButton, 0, len(s.Buttons))
|
||
for _, button := range s.Buttons {
|
||
buttons = append(buttons, backend.CreativeButton{
|
||
Text: button.Text,
|
||
URL: button.URL,
|
||
})
|
||
}
|
||
input.Buttons = &buttons
|
||
|
||
// Отправляем запрос на обновление
|
||
_, err = b.Backend.UpdateCreative(context.Background(), jwt, b.Session.WorkspaceID, s.CreativeID, input)
|
||
|
||
if err != nil {
|
||
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to update creative")
|
||
// Показываем ошибку через панель управления
|
||
s.ShowControlPanel(b, "<b>❌ Не удалось сохранить изменения</b>\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
|
||
Row(Button("← Назад", "back")),
|
||
Row(Button("✓ Попробовать снова", "confirm_save")),
|
||
})
|
||
return
|
||
}
|
||
|
||
log.Info().Str("creative_id", s.CreativeID).Msg("Creative updated successfully")
|
||
|
||
// Возвращаемся назад
|
||
if s.BackState != nil {
|
||
b.SendNew("✅ Креатив успешно обновлен!", Keyboard())
|
||
b.SetState(s.BackState, bot.EditMessage)
|
||
}
|
||
}
|
||
|
||
func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
|
||
err := b.Backend.DeleteCreative(context.Background(), b.Session.JWT, b.Session.WorkspaceID, s.CreativeID)
|
||
|
||
if err != nil {
|
||
s.confirmingDelete = false // Сбрасываем флаг подтверждения
|
||
s.ShowControlPanel(b, "<b>❌ Не удалось удалить креатив</b>\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
|
||
Row(Button("← Назад", "back")),
|
||
Row(Button("⌦ Попробовать снова", "delete_creative")),
|
||
})
|
||
return
|
||
}
|
||
|
||
if s.BackState != nil {
|
||
b.SendNew("✅ Креатив успешно удален!", Keyboard())
|
||
b.SetState(s.BackState, bot.EditMessage)
|
||
}
|
||
}
|