git_same/setup/screens/
auth.rs1use 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
10const 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 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 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;