1package main
2
3import (
4 "log"
5 "os"
6 "time"
7
8 // These imports were missing, causing all the "undefined" errors.
9 "github.com/andrinoff/email-cli/config"
10 "github.com/andrinoff/email-cli/fetcher"
11 "github.com/andrinoff/email-cli/sender"
12 "github.com/andrinoff/email-cli/tui"
13
14 tea "github.com/charmbracelet/bubbletea"
15)
16
17type modelState int
18
19const (
20 loginView modelState = iota
21 mainMenu
22 inboxView
23 emailView
24 composeView
25)
26
27// Model holds the application's state.
28type Model struct {
29 state modelState
30 login tea.Model
31 mainMenu tea.Model
32 inbox tea.Model
33 emailView tea.Model
34 composer tea.Model
35 fetcher *fetcher.Fetcher
36 sender *sender.Sender
37 emails []fetcher.Email
38 statusMessage string
39 err error
40}
41
42// New creates the initial model.
43func New() Model {
44 // We start with the login view.
45 return Model{
46 state: loginView,
47 login: tui.NewLogin(),
48 mainMenu: tui.NewChoice(),
49 }
50}
51
52// Init runs any initial commands.
53func (m Model) Init() tea.Cmd {
54 return tea.EnterAltScreen
55}
56
57// Update handles all messages and updates the model.
58func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
59 var cmd tea.Cmd
60 var cmds []tea.Cmd
61
62 // Global messages that can be handled regardless of state.
63 switch msg := msg.(type) {
64 case tea.KeyMsg:
65 if msg.String() == "ctrl+c" {
66 return m, tea.Quit
67 }
68 case tui.EmailResultMsg:
69 if msg.Err != nil {
70 m.statusMessage = "❌ Error: " + msg.Err.Error()
71 } else {
72 m.statusMessage = "✅ Email sent successfully!"
73 }
74 m.state = mainMenu
75 return m, tea.Tick(3*time.Second, func(t time.Time) tea.Msg {
76 return tui.ClearStatusMsg{}
77 })
78 case tui.ClearStatusMsg:
79 m.statusMessage = ""
80 return m, nil
81 }
82
83 // State-specific message handling.
84 switch m.state {
85 case loginView:
86 // When the user submits their credentials...
87 if msg, ok := msg.(tui.Credentials); ok {
88 // For now, we'll default to Gmail.
89 service := "gmail"
90 cfg := &config.Config{Email: msg.Email, Password: msg.Password, Service: service}
91 config.Save(cfg)
92 m.fetcher = fetcher.New(cfg)
93 m.sender = sender.New(msg.Email, msg.Password, service)
94 m.state = mainMenu
95 return m, nil
96 }
97
98 case mainMenu:
99 // Handle user's choice from the main menu.
100 if msg, ok := msg.(tea.KeyMsg); ok {
101 switch msg.String() {
102 case "1", "j", "down": // View Inbox
103 m.state = inboxView
104 m.inbox = tui.NewInbox(m.emails)
105 return m, func() tea.Msg {
106 emails, err := m.fetcher.FetchEmails("INBOX")
107 if err != nil {
108 return tui.FetchErr(err)
109 }
110 return tui.EmailsFetchedMsg{Emails: emails}
111 }
112 case "2", "k", "up": // Compose Email
113 m.state = composeView
114 m.composer = tui.NewComposer(m.fetcher.Config.Email)
115 return m, nil
116 }
117 }
118
119 case inboxView:
120 switch msg := msg.(type) {
121 case tui.EmailsFetchedMsg:
122 m.emails = msg.Emails
123 m.inbox = tui.NewInbox(m.emails)
124 return m, nil
125 case tui.ViewEmailMsg:
126 m.state = emailView
127 m.emailView = tui.NewEmailView(m.emails[msg.Index])
128 return m, nil
129 case tea.KeyMsg:
130 if msg.String() == "esc" {
131 m.state = mainMenu
132 }
133 }
134
135 case emailView:
136 if msg, ok := msg.(tea.KeyMsg); ok && msg.String() == "esc" {
137 m.state = inboxView // Go back to the inbox list.
138 }
139
140 case composeView:
141 switch msg := msg.(type) {
142 case tui.SendEmailMsg:
143 m.statusMessage = "⏳ Sending email..."
144 return m, func() tea.Msg {
145 err := m.sender.Send(msg.To, msg.Subject, msg.Body)
146 return tui.EmailResultMsg{Err: err}
147 }
148 case tea.KeyMsg:
149 if msg.String() == "esc" {
150 m.state = mainMenu
151 }
152 }
153 }
154
155 // Pass the message to the current view's Update function.
156 switch m.state {
157 case loginView:
158 m.login, cmd = m.login.Update(msg)
159 case mainMenu:
160 m.mainMenu, cmd = m.mainMenu.Update(msg)
161 case inboxView:
162 m.inbox, cmd = m.inbox.Update(msg)
163 case emailView:
164 m.emailView, cmd = m.emailView.Update(msg)
165 case composeView:
166 m.composer, cmd = m.composer.Update(msg)
167 }
168 cmds = append(cmds, cmd)
169 return m, tea.Batch(cmds...)
170}
171
172// View renders the current UI.
173func (m Model) View() string {
174 var s string
175 switch m.state {
176 case loginView:
177 s = m.login.View()
178 case mainMenu:
179 s = m.mainMenu.View()
180 case inboxView:
181 s = m.inbox.View()
182 case emailView:
183 s = m.emailView.View()
184 case composeView:
185 s = m.composer.View()
186 default:
187 s = "Unknown state."
188 }
189
190 if m.statusMessage != "" {
191 return s + "\n\n" + m.statusMessage
192 }
193 return s
194}
195
196func main() {
197 // Load config first to see if we can skip login.
198 cfg := config.Load()
199 m := New()
200 if cfg.Email != "" && cfg.Password != "" && cfg.Service != "" {
201 m.fetcher = fetcher.New(cfg)
202 m.sender = sender.New(cfg.Email, cfg.Password, cfg.Service)
203 m.state = mainMenu
204 }
205
206 p := tea.NewProgram(m)
207 if _, err := p.Run(); err != nil {
208 log.Fatal(err)
209 os.Exit(1)
210 }
211}