refactor
This commit is contained in:
359
tg_bot/screens/creative_details.go
Normal file
359
tg_bot/screens/creative_details.go
Normal file
@@ -0,0 +1,359 @@
|
||||
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
|
||||
WorkspaceID 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, s.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,
|
||||
s.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, s.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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user