Skip to main content

git_same/setup/screens/
auth.rs

1//! Step 2: Authentication screen with spinner and centered layout.
2
3use crate::setup::state::{AuthStatus, SetupState};
4use ratatui::layout::{Alignment, Rect};
5use ratatui::style::{Color, Modifier, Style};
6use ratatui::text::{Line, Span};
7use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
8use ratatui::Frame;
9
10/// Braille spinner frames.
11const SPINNER: [char; 10] = [
12    '\u{280b}', '\u{2819}', '\u{2839}', '\u{2838}', '\u{283c}', '\u{2834}', '\u{2826}', '\u{2827}',
13    '\u{2807}', '\u{280f}',
14];
15
16pub fn render(state: &SetupState, frame: &mut Frame, area: Rect) {
17    let provider = state.selected_provider();
18    let green = Style::default().fg(Color::Rgb(21, 128, 61));
19    let green_bold = green.add_modifier(Modifier::BOLD);
20
21    let mut lines: Vec<Line> = Vec::new();
22    lines.push(Line::raw(""));
23
24    // Title
25    lines.push(Line::from(Span::styled(
26        format!("Authenticate with {}", provider.display_name()),
27        Style::default()
28            .fg(Color::Cyan)
29            .add_modifier(Modifier::BOLD),
30    )));
31    lines.push(Line::raw(""));
32    lines.push(Line::from(Span::styled(
33        "Detection method: GitHub CLI (gh)",
34        Style::default().fg(Color::DarkGray),
35    )));
36    lines.push(Line::raw(""));
37
38    match &state.auth_status {
39        AuthStatus::Pending => {
40            lines.push(Line::from(Span::styled(
41                "Press Enter to authenticate...",
42                Style::default().fg(Color::Yellow),
43            )));
44        }
45        AuthStatus::Checking => {
46            let spinner_char = SPINNER[(state.tick_count as usize) % SPINNER.len()];
47            lines.push(Line::from(Span::styled(
48                format!("{} Authenticating...", spinner_char),
49                Style::default().fg(Color::Yellow),
50            )));
51        }
52        AuthStatus::Success => {
53            lines.push(Line::from(Span::styled(
54                "\u{2713} Authenticated",
55                green_bold,
56            )));
57            if let Some(ref username) = state.username {
58                lines.push(Line::from(vec![
59                    Span::styled("Logged in as: ", Style::default().fg(Color::DarkGray)),
60                    Span::styled(
61                        format!("@{}", username),
62                        Style::default()
63                            .fg(Color::Cyan)
64                            .add_modifier(Modifier::BOLD),
65                    ),
66                ]));
67            }
68            lines.push(Line::raw(""));
69            lines.push(Line::from(Span::styled(
70                "Press Enter to continue",
71                Style::default().fg(Color::DarkGray),
72            )));
73        }
74        AuthStatus::Failed(msg) => {
75            lines.push(Line::from(Span::styled(
76                "\u{2717} Authentication failed",
77                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
78            )));
79            lines.push(Line::raw(""));
80            lines.push(Line::from(Span::styled(
81                msg.as_str(),
82                Style::default().fg(Color::White),
83            )));
84            lines.push(Line::raw(""));
85            lines.push(Line::from(Span::styled(
86                "Ensure gh is installed and run: gh auth login",
87                Style::default().fg(Color::DarkGray),
88            )));
89        }
90    }
91
92    let content = Paragraph::new(lines).alignment(Alignment::Center);
93
94    // Error block styling for failed state
95    let block = if matches!(state.auth_status, AuthStatus::Failed(_)) {
96        Block::default()
97            .borders(Borders::ALL)
98            .border_type(BorderType::Plain)
99            .border_style(Style::default().fg(Color::Red))
100            .title(" Error ")
101            .title_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
102    } else {
103        Block::default()
104    };
105
106    frame.render_widget(content.block(block), area);
107}
108
109#[cfg(test)]
110#[path = "auth_tests.rs"]
111mod tests;