Skip to main content

max / makenotwork

3.9 KB · 128 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(crate) 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(
21 " Makenot.work ",
22 Style::default().add_modifier(Modifier::BOLD),
23 ),
24 Span::raw(" -- "),
25 Span::styled(project_title, Style::default().add_modifier(Modifier::BOLD)),
26 Span::raw(" -- "),
27 Span::styled(
28 "Subscription Tiers",
29 Style::default().add_modifier(Modifier::BOLD),
30 ),
31 Span::raw(" "),
32 ]);
33
34 let block = Block::default()
35 .title(title)
36 .borders(Borders::ALL)
37 .border_style(Style::default().fg(Color::Gray));
38
39 let inner = block.inner(area);
40 frame.render_widget(block, area);
41
42 let chunks = Layout::vertical([
43 Constraint::Length(1), // spacer
44 Constraint::Length(1), // section header
45 Constraint::Min(3), // list
46 Constraint::Length(1), // status line
47 Constraint::Length(1), // keybindings
48 ])
49 .split(inner);
50
51 let count = app.tiers.len();
52 let header = Paragraph::new(Line::from(vec![
53 Span::raw(" "),
54 Span::styled("Tiers", Style::default().add_modifier(Modifier::BOLD)),
55 if count == 0 {
56 Span::raw("")
57 } else {
58 Span::raw(format!(" ({count})"))
59 },
60 ]));
61 frame.render_widget(header, chunks[1]);
62
63 if app.loading {
64 let loading = Paragraph::new(" Loading...");
65 frame.render_widget(loading, chunks[2]);
66 } else if app.tiers.is_empty() {
67 let empty = Paragraph::new(" No subscription tiers for this project.");
68 frame.render_widget(empty, chunks[2]);
69 } else {
70 let rows: Vec<Row> = app
71 .tiers
72 .iter()
73 .enumerate()
74 .map(|(i, t)| {
75 let status = if t.is_active { "active" } else { "inactive" };
76 let desc = if t.description.len() > 30 {
77 format!("{}...", &t.description[..27])
78 } else {
79 t.description.clone()
80 };
81 Row::new(vec![
82 format!(" {}", t.name),
83 format::format_price(t.price_cents, app.currency()),
84 status.to_string(),
85 desc,
86 ])
87 .style(widgets::selected_style(i, Some(app.selected_index)))
88 })
89 .collect();
90
91 let widths = [
92 Constraint::Min(16),
93 Constraint::Length(10),
94 Constraint::Length(10),
95 Constraint::Length(30),
96 ];
97
98 widgets::render_table(
99 frame,
100 chunks[2],
101 &[" Name", "Price", "Status", "Description"],
102 &widths,
103 rows,
104 );
105 }
106
107 if let Some(ref status) = app.tiers_status {
108 let style = if status.starts_with("Error") {
109 Style::default().fg(Color::Red)
110 } else {
111 Style::default().fg(Color::Green)
112 };
113 let status_line = Paragraph::new(format!(" {status}")).style(style);
114 frame.render_widget(status_line, chunks[3]);
115 }
116
117 let key_spans = vec![
118 Span::raw(" "),
119 Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)),
120 Span::raw(" Nav "),
121 Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)),
122 Span::raw(" Back"),
123 ];
124
125 let keys = Paragraph::new(Line::from(key_spans)).style(Style::default().fg(Color::DarkGray));
126 frame.render_widget(keys, chunks[4]);
127 }
128