//! Subscription tier list screen — read-only view of project tiers. use makeover_tui::makeover_layout::{Column, Priority, Width}; use makeover_tui::table::{self, Cell, Sizing, TableStyle}; use ratatui::Frame; use ratatui::layout::{Constraint, Layout}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Paragraph, TableState}; use crate::format; use super::App; /// The subscription tier columns. A tier is a name and a price, so both are /// essential; the description is the first thing to go. const TIER_COLUMNS: [Column<'static>; 4] = [ Column { name: "Name", width: Width::Fill, priority: Priority::Essential, sortable: false, sorted: None, }, Column { name: "Price", width: Width::Fixed, priority: Priority::Essential, sortable: false, sorted: None, }, Column { name: "Status", width: Width::Fixed, priority: Priority::Secondary, sortable: false, sorted: None, }, Column { name: "Description", width: Width::Fixed, priority: Priority::Optional, sortable: false, sorted: None, }, ]; /// The tracks the hand-written `Constraint`s carried. The description is /// truncated to fit its 30, which is why it is a fixed track and not a fill. const TIER_SIZING: Sizing<'static> = Sizing { lengths: &[ ("Name", 16), ("Price", 10), ("Status", 10), ("Description", 30), ], fallback: 10, }; pub(crate) fn render(frame: &mut Frame, app: &App) { let area = frame.area(); let project_title = app.tiers_project_title.as_deref().unwrap_or("Project"); let title = Line::from(vec![ Span::styled( " Makenot.work ", Style::default().add_modifier(Modifier::BOLD), ), Span::raw(" -- "), Span::styled(project_title, Style::default().add_modifier(Modifier::BOLD)), Span::raw(" -- "), Span::styled( "Subscription Tiers", Style::default().add_modifier(Modifier::BOLD), ), Span::raw(" "), ]); let block = Block::default() .title(title) .borders(Borders::ALL) .border_style(Style::default().fg(app.theme.line_border)); let inner = block.inner(area); frame.render_widget(block, area); let chunks = Layout::vertical([ Constraint::Length(1), // spacer Constraint::Length(1), // section header Constraint::Min(3), // list Constraint::Length(1), // status line Constraint::Length(1), // keybindings ]) .split(inner); let count = app.tiers.len(); let header = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled("Tiers", Style::default().add_modifier(Modifier::BOLD)), if count == 0 { Span::raw("") } else { Span::raw(format!(" ({count})")) }, ])); frame.render_widget(header, chunks[1]); if app.loading { let loading = Paragraph::new(" Loading..."); frame.render_widget(loading, chunks[2]); } else if app.tiers.is_empty() { let empty = Paragraph::new(" No subscription tiers for this project."); frame.render_widget(empty, chunks[2]); } else { let area = super::indent(chunks[2], 2); let rows: Vec> = app .tiers .iter() .map(|t| { let desc = if t.description.len() > 30 { format!("{}...", &t.description[..27]) } else { t.description.clone() }; vec![ Cell::new("Name", t.name.as_str()), Cell::new("Price", format::format_price(t.price_cents, app.currency())), Cell::new("Status", if t.is_active { "active" } else { "inactive" }), Cell::new("Description", desc), ] }) .collect(); let style = TableStyle::from_theme(&app.theme); let table = table::table(&TIER_COLUMNS, &rows, &TIER_SIZING, &style, area.width); let mut state = TableState::default().with_selected(Some(app.selected_index)); frame.render_stateful_widget(table, area, &mut state); } if let Some(ref status) = app.tiers_status { let style = if status.starts_with("Error") { Style::default().fg(app.theme.status_danger) } else { Style::default().fg(app.theme.status_success) }; let status_line = Paragraph::new(format!(" {status}")).style(style); frame.render_widget(status_line, chunks[3]); } let key_spans = vec![ Span::raw(" "), Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Nav "), Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Back"), ]; let keys = Paragraph::new(Line::from(key_spans)).style(Style::default().fg(app.theme.content_muted)); frame.render_widget(keys, chunks[4]); }