Skip to main content

max / makenotwork

3.7 KB · 112 lines History Blame Raw
1 //! Subscription tier list screen — read-only view of project tiers.
2
3 use ratatui::Frame;
4 use ratatui::layout::{Constraint, Layout};
5 use ratatui::style::{Color, Modifier, Style};
6 use ratatui::text::{Line, Span};
7 use ratatui::widgets::{Block, Borders, Paragraph, Row};
8
9 use crate::format;
10
11 use super::App;
12 use super::widgets;
13
14 pub fn render(frame: &mut Frame, app: &App) {
15 let area = frame.area();
16
17 let project_title = app.tiers_project_title.as_deref().unwrap_or("Project");
18
19 let title = Line::from(vec![
20 Span::styled(" Makenot.work ", Style::default().add_modifier(Modifier::BOLD)),
21 Span::raw(" -- "),
22 Span::styled(project_title, Style::default().add_modifier(Modifier::BOLD)),
23 Span::raw(" -- "),
24 Span::styled("Subscription Tiers", Style::default().add_modifier(Modifier::BOLD)),
25 Span::raw(" "),
26 ]);
27
28 let block = Block::default()
29 .title(title)
30 .borders(Borders::ALL)
31 .border_style(Style::default().fg(Color::Gray));
32
33 let inner = block.inner(area);
34 frame.render_widget(block, area);
35
36 let chunks = Layout::vertical([
37 Constraint::Length(1), // spacer
38 Constraint::Length(1), // section header
39 Constraint::Min(3), // list
40 Constraint::Length(1), // status line
41 Constraint::Length(1), // keybindings
42 ])
43 .split(inner);
44
45 let count = app.tiers.len();
46 let header = Paragraph::new(Line::from(vec![
47 Span::raw(" "),
48 Span::styled("Tiers", Style::default().add_modifier(Modifier::BOLD)),
49 if count == 0 { Span::raw("") } else { Span::raw(format!(" ({})", count)) },
50 ]));
51 frame.render_widget(header, chunks[1]);
52
53 if app.loading {
54 let loading = Paragraph::new(" Loading...");
55 frame.render_widget(loading, chunks[2]);
56 } else if app.tiers.is_empty() {
57 let empty = Paragraph::new(" No subscription tiers for this project.");
58 frame.render_widget(empty, chunks[2]);
59 } else {
60 let rows: Vec<Row> = app
61 .tiers
62 .iter()
63 .enumerate()
64 .map(|(i, t)| {
65 let status = if t.is_active { "active" } else { "inactive" };
66 let desc = if t.description.len() > 30 {
67 format!("{}...", &t.description[..27])
68 } else {
69 t.description.clone()
70 };
71 Row::new(vec![
72 format!(" {}", t.name),
73 format::format_price(t.price_cents),
74 status.to_string(),
75 desc,
76 ])
77 .style(widgets::selected_style(i, Some(app.selected_index)))
78 })
79 .collect();
80
81 let widths = [
82 Constraint::Min(16),
83 Constraint::Length(10),
84 Constraint::Length(10),
85 Constraint::Length(30),
86 ];
87
88 widgets::render_table(frame, chunks[2], &[" Name", "Price", "Status", "Description"], &widths, rows);
89 }
90
91 if let Some(ref status) = app.tiers_status {
92 let style = if status.starts_with("Error") {
93 Style::default().fg(Color::Red)
94 } else {
95 Style::default().fg(Color::Green)
96 };
97 let status_line = Paragraph::new(format!(" {}", status)).style(style);
98 frame.render_widget(status_line, chunks[3]);
99 }
100
101 let key_spans = vec![
102 Span::raw(" "),
103 Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)),
104 Span::raw(" Nav "),
105 Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)),
106 Span::raw(" Back"),
107 ];
108
109 let keys = Paragraph::new(Line::from(key_spans)).style(Style::default().fg(Color::DarkGray));
110 frame.render_widget(keys, chunks[4]);
111 }
112