//! Settings screen — profile info, SSH keys, storage meter. use ratatui::Frame; use ratatui::layout::{Constraint, Layout}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Gauge, Paragraph, Row}; use crate::format; use crate::staging; use super::App; use super::widgets; pub fn render(frame: &mut Frame, app: &App) { let area = frame.area(); let title = Line::from(vec![ Span::styled(" Makenot.work ", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" -- "), Span::styled("Settings", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" "), ]); let block = Block::default() .title(title) .borders(Borders::ALL) .border_style(Style::default().fg(Color::Gray)); let inner = block.inner(area); frame.render_widget(block, area); let chunks = Layout::vertical([ Constraint::Length(1), // spacer Constraint::Length(1), // profile header Constraint::Length(3), // profile info Constraint::Length(1), // spacer Constraint::Length(1), // storage header Constraint::Length(1), // storage bar Constraint::Length(1), // spacer Constraint::Length(1), // SSH keys header Constraint::Min(3), // SSH keys table Constraint::Length(1), // status Constraint::Length(1), // keybindings ]) .split(inner); // Profile header let profile_header = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled("Profile", Style::default().add_modifier(Modifier::BOLD)), ])); frame.render_widget(profile_header, chunks[1]); // Profile info let tier = app .user .creator_tier .as_deref() .map(format::format_tier) .unwrap_or("No tier"); let display_name = app .user .display_name .as_deref() .unwrap_or("(not set)"); let profile_lines = vec![ Line::from(vec![ Span::raw(" Username: "), Span::styled( &app.user.username, Style::default().add_modifier(Modifier::BOLD), ), ]), Line::from(vec![ Span::raw(" Display: "), Span::raw(display_name), ]), Line::from(vec![ Span::raw(" Tier: "), Span::styled(tier, Style::default().fg(Color::Cyan)), ]), ]; let profile = Paragraph::new(profile_lines); frame.render_widget(profile, chunks[2]); // Storage header let storage_header = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled("Storage", Style::default().add_modifier(Modifier::BOLD)), ])); frame.render_widget(storage_header, chunks[4]); // Storage gauge if let Some(ref info) = app.storage_info { let pct = if info.max_storage_bytes > 0 { (info.storage_used_bytes as f64 / info.max_storage_bytes as f64 * 100.0) as u16 } else { 0 }; let label = format!( " {} / {} ({}%)", staging::format_bytes(info.storage_used_bytes as u64), staging::format_bytes(info.max_storage_bytes as u64), pct, ); let color = if pct > 90 { Color::Red } else if pct > 70 { Color::Yellow } else { Color::Green }; let gauge = Gauge::default() .gauge_style(Style::default().fg(color)) .label(Span::raw(label)) .ratio(pct as f64 / 100.0); frame.render_widget(gauge, chunks[5]); } else { let no_storage = Paragraph::new(" No storage data available."); frame.render_widget(no_storage, chunks[5]); } // SSH keys header let count = app.ssh_keys.len(); let keys_header = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled("SSH Keys", Style::default().add_modifier(Modifier::BOLD)), if count == 0 { Span::raw("") } else { Span::raw(format!(" ({})", count)) }, ])); frame.render_widget(keys_header, chunks[7]); // SSH keys table if app.loading { let loading = Paragraph::new(" Loading..."); frame.render_widget(loading, chunks[8]); } else if app.ssh_keys.is_empty() { let empty = Paragraph::new(" No SSH keys registered."); frame.render_widget(empty, chunks[8]); } else { let rows: Vec = app .ssh_keys .iter() .enumerate() .map(|(i, key)| { let fp = key.fingerprint.get(..20).unwrap_or(&key.fingerprint); let date = key.created_at.get(..10).unwrap_or(&key.created_at); Row::new(vec![ format!(" {}", key.label), format!("{}...", fp), date.to_string(), ]) .style(widgets::selected_style(i, Some(app.selected_index))) }) .collect(); let widths = [ Constraint::Min(20), Constraint::Length(24), Constraint::Length(12), ]; widgets::render_table(frame, chunks[8], &[" Label", "Fingerprint", "Added"], &widths, rows); } // Status line if let Some(ref status) = app.settings_status { let style = if status.starts_with("Error") { Style::default().fg(Color::Red) } else { Style::default().fg(Color::Green) }; let status_line = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled(status.as_str(), style), ])); frame.render_widget(status_line, chunks[9]); } // Keybindings let key_spans = vec![ Span::raw(" "), Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Nav "), Span::styled("[r]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Refresh "), Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Back"), ]; let keys_bar = Paragraph::new(Line::from(key_spans)).style(Style::default().fg(Color::DarkGray)); frame.render_widget(keys_bar, chunks[10]); }