login.go

  1package tui
  2
  3import (
  4	"strings"
  5
  6	"github.com/andrinoff/email-cli/config"
  7	"github.com/charmbracelet/bubbles/cursor"
  8	"github.com/charmbracelet/bubbles/textinput"
  9	tea "github.com/charmbracelet/bubbletea"
 10	"github.com/charmbracelet/lipgloss"
 11)
 12
 13type loginModel struct {
 14	focusIndex int
 15	inputs     []textinput.Model
 16	cursorMode cursor.Mode
 17	config     *config.Config
 18	err        error
 19}
 20
 21func initialLoginModel() loginModel {
 22	m := loginModel{
 23		inputs: make([]textinput.Model, 3),
 24	}
 25
 26	var t textinput.Model
 27	for i := range m.inputs {
 28		t = textinput.New()
 29		t.Cursor.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
 30		t.CharLimit = 32
 31
 32		switch i {
 33		case 0:
 34			t.Placeholder = "gmail or icloud"
 35			t.Focus()
 36			t.Prompt = "> "
 37			t.CharLimit = 10
 38		case 1:
 39			t.Placeholder = "your@email.com"
 40			t.Prompt = "  "
 41			t.CharLimit = 64
 42		case 2:
 43			t.Placeholder = "App-specific password"
 44			t.Prompt = "  "
 45			t.EchoMode = textinput.EchoPassword
 46			t.EchoCharacter = '•'
 47			t.CharLimit = 64
 48		}
 49		m.inputs[i] = t
 50	}
 51
 52	return m
 53}
 54
 55func (m loginModel) Init() tea.Cmd { return textinput.Blink }
 56
 57func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 58	switch msg := msg.(type) {
 59	case tea.KeyMsg:
 60		switch msg.String() {
 61		case "ctrl+c", "esc":
 62			return m, tea.Quit
 63		case "tab", "shift+tab", "enter", "up", "down":
 64			s := msg.String()
 65			if s == "enter" && m.focusIndex == len(m.inputs) {
 66				m.config = &config.Config{
 67					ServiceProvider: m.inputs[0].Value(),
 68					Email:           m.inputs[1].Value(),
 69					Password:        m.inputs[2].Value(),
 70				}
 71				if err := config.SaveConfig(m.config); err != nil {
 72					m.err = err
 73					return m, nil
 74				}
 75				return m, tea.Quit
 76			}
 77			if s == "up" || s == "shift+tab" {
 78				m.focusIndex--
 79			} else {
 80				m.focusIndex++
 81			}
 82			if m.focusIndex > len(m.inputs) {
 83				m.focusIndex = 0
 84			} else if m.focusIndex < 0 {
 85				m.focusIndex = len(m.inputs)
 86			}
 87			cmds := make([]tea.Cmd, len(m.inputs))
 88			for i := 0; i < len(m.inputs); i++ {
 89				if i == m.focusIndex {
 90					cmds[i] = m.inputs[i].Focus()
 91					m.inputs[i].Prompt = "> "
 92					continue
 93				}
 94				m.inputs[i].Blur()
 95				m.inputs[i].Prompt = "  "
 96			}
 97			return m, tea.Batch(cmds...)
 98		}
 99	}
100	cmd := m.updateInputs(msg)
101	return m, cmd
102}
103
104func (m *loginModel) updateInputs(msg tea.Msg) tea.Cmd {
105	cmds := make([]tea.Cmd, len(m.inputs))
106	for i := range m.inputs {
107		m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
108	}
109	return tea.Batch(cmds...)
110}
111
112func (m loginModel) View() string {
113	var b strings.Builder
114	b.WriteString(InfoStyle.Render(" Welcome to email-cli! Please configure your email account. ") + "\n\n")
115	for i := range m.inputs {
116		b.WriteString(m.inputs[i].View())
117		if i < len(m.inputs)-1 {
118			b.WriteRune('\n')
119		}
120	}
121	button := "\n\n" + "[ Submit ]"
122	if m.focusIndex == len(m.inputs) {
123		button = "\n\n" + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("[ Submit ]")
124	}
125	b.WriteString(button)
126	if m.err != nil {
127		b.WriteString("\n\nError: " + m.err.Error())
128	}
129	b.WriteString(HelpStyle.Render("\n\n(tab to navigate, enter to submit, esc to quit)"))
130	return DialogBoxStyle.Render(b.String())
131}
132
133// RunLogin starts the login UI and returns the created config.
134func RunLogin() (*config.Config, error) {
135	p := tea.NewProgram(initialLoginModel())
136	m, err := p.Run()
137	if err != nil {
138		return nil, err
139	}
140	finalModel := m.(loginModel)
141	return finalModel.config, nil
142}