- Changed 'q' key to 'b' for back navigation in client details - Added 'esc' key binding for back navigation - Updated help text to reflect new key bindings - Created new DeleteConfirmModal component with name verification - User must type exact client name to confirm deletion (safety feature) - Improved modal styling with visual feedback (red/green indicators) - Case-sensitive name matching to prevent accidental deletions Fixes: wg-admin-az7
198 lines
4.8 KiB
Go
198 lines
4.8 KiB
Go
package components
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
// DeleteConfirmModel represents a deletion confirmation modal with name verification
|
|
type DeleteConfirmModel struct {
|
|
clientName string
|
|
input textinput.Model
|
|
Visible bool
|
|
Width int
|
|
Height int
|
|
showErrorMessage bool
|
|
}
|
|
|
|
// Styles
|
|
var (
|
|
deleteModalStyle = lipgloss.NewStyle().
|
|
Border(lipgloss.RoundedBorder()).
|
|
BorderForeground(lipgloss.Color("196")).
|
|
Padding(1, 3).
|
|
Background(lipgloss.Color("235"))
|
|
deleteTitleStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("226")).
|
|
Bold(true)
|
|
deleteMessageStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("255")).
|
|
Width(60)
|
|
deleteWarningStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("196")).
|
|
Bold(true).
|
|
MarginTop(1)
|
|
deleteInputStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("255")).
|
|
Width(60)
|
|
deleteHelpStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("241")).
|
|
MarginTop(1)
|
|
deleteErrorStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("196")).
|
|
Bold(true).
|
|
MarginTop(1)
|
|
deleteSuccessStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("46")).
|
|
Bold(true).
|
|
MarginTop(1)
|
|
)
|
|
|
|
// NewDeleteConfirm creates a new deletion confirmation modal
|
|
func NewDeleteConfirm(clientName string, width, height int) *DeleteConfirmModel {
|
|
ti := textinput.New()
|
|
ti.Placeholder = "Type client name to confirm"
|
|
ti.Focus()
|
|
ti.CharLimit = 100
|
|
ti.Width = 40
|
|
|
|
return &DeleteConfirmModel{
|
|
clientName: clientName,
|
|
input: ti,
|
|
Visible: true,
|
|
Width: width,
|
|
Height: height,
|
|
showErrorMessage: false,
|
|
}
|
|
}
|
|
|
|
// Init initializes the deletion confirmation modal
|
|
func (m *DeleteConfirmModel) Init() tea.Cmd {
|
|
return textinput.Blink
|
|
}
|
|
|
|
// Update handles messages for the deletion confirmation modal
|
|
func (m *DeleteConfirmModel) Update(msg tea.Msg) (*DeleteConfirmModel, tea.Cmd) {
|
|
var cmd tea.Cmd
|
|
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "esc":
|
|
m.Visible = false
|
|
return m, nil
|
|
case "enter":
|
|
if m.input.Value() == m.clientName {
|
|
m.Visible = false
|
|
return m, nil
|
|
}
|
|
m.showErrorMessage = true
|
|
return m, nil
|
|
}
|
|
}
|
|
|
|
m.input, cmd = m.input.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
// View renders the deletion confirmation modal
|
|
func (m *DeleteConfirmModel) View() string {
|
|
if !m.Visible {
|
|
return ""
|
|
}
|
|
|
|
// Check if input matches client name
|
|
matches := m.input.Value() == m.clientName
|
|
|
|
// Build warning section
|
|
warningText := deleteWarningStyle.Render(
|
|
fmt.Sprintf("⚠️ This will permanently delete client '%s'", m.clientName),
|
|
)
|
|
|
|
// Build message section
|
|
messageText := deleteMessageStyle.Render(
|
|
fmt.Sprintf("This action cannot be undone. Please type the client name to confirm deletion."),
|
|
)
|
|
|
|
// Build input section
|
|
inputSection := lipgloss.JoinVertical(
|
|
lipgloss.Left,
|
|
"",
|
|
deleteInputStyle.Render("Client name:"),
|
|
m.input.View(),
|
|
)
|
|
|
|
// Build status section
|
|
var statusText string
|
|
if matches {
|
|
statusText = deleteSuccessStyle.Render("✓ Client name matches. Press Enter to confirm deletion.")
|
|
} else if m.showErrorMessage {
|
|
statusText = deleteErrorStyle.Render("✗ Client name does not match. Please try again.")
|
|
} else if m.input.Value() != "" {
|
|
statusText = deleteHelpStyle.Render("Client name does not match yet...")
|
|
} else {
|
|
statusText = deleteHelpStyle.Render("Type the client name to enable confirmation.")
|
|
}
|
|
|
|
// Build help section
|
|
helpText := deleteHelpStyle.Render("Esc to cancel • Enter to confirm (when name matches)")
|
|
|
|
// Build modal content
|
|
content := lipgloss.JoinVertical(
|
|
lipgloss.Left,
|
|
deleteTitleStyle.Render("🗑️ Delete Client"),
|
|
"",
|
|
warningText,
|
|
"",
|
|
messageText,
|
|
inputSection,
|
|
statusText,
|
|
"",
|
|
helpText,
|
|
)
|
|
|
|
// Apply modal style
|
|
modal := deleteModalStyle.Render(content)
|
|
|
|
// Center modal on screen
|
|
modalWidth := lipgloss.Width(modal)
|
|
modalHeight := lipgloss.Height(modal)
|
|
|
|
x := (m.Width - modalWidth) / 2
|
|
if x < 0 {
|
|
x = 0
|
|
}
|
|
y := (m.Height - modalHeight) / 2
|
|
if y < 0 {
|
|
y = 0
|
|
}
|
|
|
|
return lipgloss.Place(m.Width, m.Height,
|
|
lipgloss.Left, lipgloss.Top,
|
|
modal,
|
|
lipgloss.WithWhitespaceChars(" "),
|
|
lipgloss.WithWhitespaceForeground(lipgloss.Color("235")),
|
|
)
|
|
}
|
|
|
|
// IsConfirmed returns true if user confirmed the deletion
|
|
func (m *DeleteConfirmModel) IsConfirmed() bool {
|
|
return !m.Visible && m.input.Value() == m.clientName
|
|
}
|
|
|
|
// IsCancelled returns true if user cancelled
|
|
func (m *DeleteConfirmModel) IsCancelled() bool {
|
|
return !m.Visible && m.input.Value() != m.clientName
|
|
}
|
|
|
|
// Reset resets the modal for reuse
|
|
func (m *DeleteConfirmModel) Reset() {
|
|
m.input.SetValue("")
|
|
m.input.Reset()
|
|
m.Visible = true
|
|
m.showErrorMessage = false
|
|
}
|