use super::{utils::centered_rect_fixed, Component};
use crate::{
action::Action,
mode::{InputMode, Scene},
style::{clear_area, EUCALYPTUS, GHOST_WHITE, LIGHT_PERIWINKLE, VIVID_SKY_BLUE},
widgets::hyperlink::Hyperlink,
};
use color_eyre::Result;
use crossterm::event::{Event, KeyCode, KeyEvent};
use ratatui::{prelude::*, widgets::*};
use tui_input::{backend::crossterm::EventHandler, Input};
pub struct BetaProgramme {
active: bool,
state: BetaProgrammeState,
discord_input_filed: Input,
old_value: String,
}
enum BetaProgrammeState {
DiscordIdAlreadySet,
ShowTCs,
RejectTCs,
AcceptTCsAndEnterDiscordId,
}
impl BetaProgramme {
pub fn new(username: String) -> Self {
let state = if username.is_empty() {
BetaProgrammeState::ShowTCs
} else {
BetaProgrammeState::DiscordIdAlreadySet
};
Self {
active: false,
state,
discord_input_filed: Input::default().with_value(username),
old_value: Default::default(),
}
}
fn capture_inputs(&mut self, key: KeyEvent) -> Vec<Action> {
let send_back = match key.code {
KeyCode::Enter => {
let username = self.discord_input_filed.value().to_string();
if username.is_empty() {
debug!("Got Enter, but username is empty, ignoring.");
return vec![];
}
debug!(
"Got Enter, saving the discord username {username:?} and switching to DiscordIdAlreadySet, and Home Scene",
);
self.state = BetaProgrammeState::DiscordIdAlreadySet;
vec![
Action::StoreDiscordUserName(self.discord_input_filed.value().to_string()),
Action::SwitchScene(Scene::Home),
]
}
KeyCode::Esc => {
debug!(
"Got Esc, restoring the old value {} and switching to home",
self.old_value
);
self.discord_input_filed = self
.discord_input_filed
.clone()
.with_value(self.old_value.clone());
vec![Action::SwitchScene(Scene::Home)]
}
KeyCode::Char(' ') => vec![],
KeyCode::Backspace => {
self.discord_input_filed.handle_event(&Event::Key(key));
vec![]
}
_ => {
if self.discord_input_filed.value().chars().count() < 32 {
self.discord_input_filed.handle_event(&Event::Key(key));
}
vec![]
}
};
send_back
}
}
impl Component for BetaProgramme {
fn handle_key_events(&mut self, key: KeyEvent) -> Result<Vec<Action>> {
if !self.active {
return Ok(vec![]);
}
let send_back = match &self.state {
BetaProgrammeState::DiscordIdAlreadySet => self.capture_inputs(key),
BetaProgrammeState::ShowTCs => {
match key.code {
KeyCode::Char('y') | KeyCode::Char('Y') => {
let is_discord_id_set = !self.discord_input_filed.value().is_empty();
if is_discord_id_set {
debug!("User accepted the TCs, but discord id already set, moving to DiscordIdAlreadySet");
self.state = BetaProgrammeState::DiscordIdAlreadySet;
} else {
debug!("User accepted the TCs, but no discord id set, moving to AcceptTCsAndEnterDiscordId");
self.state = BetaProgrammeState::AcceptTCsAndEnterDiscordId;
}
}
KeyCode::Esc => {
debug!("User rejected the TCs, moving to RejectTCs");
self.state = BetaProgrammeState::RejectTCs;
}
_ => {}
}
vec![]
}
BetaProgrammeState::RejectTCs => {
if let KeyCode::Esc = key.code {
debug!("RejectTCs msg closed. Switching to home scene.");
self.state = BetaProgrammeState::ShowTCs;
}
vec![Action::SwitchScene(Scene::Home)]
}
BetaProgrammeState::AcceptTCsAndEnterDiscordId => self.capture_inputs(key),
};
Ok(send_back)
}
fn update(&mut self, action: Action) -> Result<Option<Action>> {
let send_back = match action {
Action::SwitchScene(scene) => match scene {
Scene::BetaProgramme => {
self.active = true;
self.old_value = self.discord_input_filed.value().to_string();
Some(Action::SwitchInputMode(InputMode::Entry))
}
_ => {
self.active = false;
None
}
},
_ => None,
};
Ok(send_back)
}
fn draw(&mut self, f: &mut crate::tui::Frame<'_>, area: Rect) -> Result<()> {
if !self.active {
return Ok(());
}
let layer_zero = centered_rect_fixed(52, 15, area);
let layer_one = Layout::new(
Direction::Vertical,
[
Constraint::Length(2),
Constraint::Min(1),
Constraint::Length(1),
],
)
.split(layer_zero);
let pop_up_border = Paragraph::new("").block(
Block::default()
.borders(Borders::ALL)
.title("Beta Rewards Program")
.title_style(Style::new().fg(VIVID_SKY_BLUE))
.padding(Padding::uniform(2))
.border_style(Style::new().fg(VIVID_SKY_BLUE)),
);
clear_area(f, layer_zero);
match self.state {
BetaProgrammeState::DiscordIdAlreadySet => {
let layer_two = Layout::new(
Direction::Vertical,
[
Constraint::Length(3),
Constraint::Length(2),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(1),
],
)
.split(layer_one[1]);
let prompt = Paragraph::new("Discord Username associated with this device:")
.alignment(Alignment::Center)
.fg(GHOST_WHITE);
f.render_widget(prompt, layer_two[0]);
let input = Paragraph::new(self.discord_input_filed.value())
.alignment(Alignment::Center)
.fg(VIVID_SKY_BLUE);
f.set_cursor(
layer_two[1].x
+ (layer_two[1].width / 2) as u16
+ (self.discord_input_filed.value().len() / 2) as u16
+ if self.discord_input_filed.value().len() % 2 != 0 {
1
} else {
0
},
layer_two[1].y,
);
f.render_widget(input, layer_two[1]);
let text = Paragraph::new(" Changing the username will lead to loss of earned.\n rewards. The nodes will be restarted to reflect\n the change.");
f.render_widget(text.fg(GHOST_WHITE), layer_two[2]);
let dash = Block::new()
.borders(Borders::BOTTOM)
.border_style(Style::new().fg(GHOST_WHITE));
f.render_widget(dash, layer_two[3]);
let buttons_layer = Layout::horizontal(vec![
Constraint::Percentage(50),
Constraint::Percentage(50),
])
.split(layer_two[4]);
let button_no = Line::from(vec![Span::styled(
" No, Cancel [Esc]",
Style::default().fg(LIGHT_PERIWINKLE),
)]);
f.render_widget(button_no, buttons_layer[0]);
let button_yes_style = if self.discord_input_filed.value().is_empty() {
Style::default().fg(LIGHT_PERIWINKLE)
} else {
Style::default().fg(EUCALYPTUS)
};
let button_yes = Line::from(vec![Span::styled(
"Modify Username [Enter]",
button_yes_style,
)]);
f.render_widget(button_yes, buttons_layer[1]);
}
BetaProgrammeState::ShowTCs => {
let layer_two = Layout::new(
Direction::Vertical,
[
Constraint::Length(6),
Constraint::Length(1),
Constraint::Length(5),
Constraint::Length(1),
],
)
.split(layer_one[1]);
let text = Paragraph::new(" Earn a slice of millions of tokens created at\n the genesis of the Autonomi Network by running\n nodes to build and test the Beta.\n\n To continue in the beta Rewards Program you\n agree to the Terms and Conditions found here:");
f.render_widget(text.fg(GHOST_WHITE), layer_two[0]);
let link = Hyperlink::new(
Span::styled(
" https://autonomi.com/beta/terms",
Style::default().fg(VIVID_SKY_BLUE),
),
"https://autonomi.com/beta/terms",
);
f.render_widget_ref(link, layer_two[1]);
let dash = Block::new()
.borders(Borders::BOTTOM)
.border_style(Style::new().fg(GHOST_WHITE));
f.render_widget(dash, layer_two[2]);
let buttons_layer = Layout::horizontal(vec![
Constraint::Percentage(45),
Constraint::Percentage(55),
])
.split(layer_two[3]);
let button_no = Line::from(vec![Span::styled(
" No, Cancel [Esc]",
Style::default().fg(LIGHT_PERIWINKLE),
)]);
f.render_widget(button_no, buttons_layer[0]);
let button_yes = Line::from(vec![Span::styled(
"Yes, I agree! Continue [Y]",
Style::default().fg(EUCALYPTUS),
)]);
f.render_widget(button_yes, buttons_layer[1]);
}
BetaProgrammeState::RejectTCs => {
let layer_two = Layout::new(
Direction::Vertical,
[
Constraint::Length(7),
Constraint::Length(5),
Constraint::Length(1),
],
)
.split(layer_one[1]);
let text = Paragraph::new(" Terms and conditions not accepted\n Beta Rewards Program entry not approved\n You can still run nodes on the network, but\n you will not be part of the Beta Rewards\n Program.\n");
f.render_widget(text.fg(GHOST_WHITE), layer_two[0]);
let dash = Block::new()
.borders(Borders::BOTTOM)
.border_style(Style::new().fg(GHOST_WHITE));
f.render_widget(dash, layer_two[1]);
let line = Line::from(vec![Span::styled(
" Close [Esc]",
Style::default().fg(LIGHT_PERIWINKLE),
)]);
f.render_widget(line, layer_two[2]);
}
BetaProgrammeState::AcceptTCsAndEnterDiscordId => {
let layer_two = Layout::new(
Direction::Vertical,
[
Constraint::Length(3),
Constraint::Length(2),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(1),
],
)
.split(layer_one[1]);
let prompt =
Paragraph::new("Enter your Discord Username").alignment(Alignment::Center);
f.render_widget(prompt.fg(GHOST_WHITE), layer_two[0]);
let input = Paragraph::new(self.discord_input_filed.value())
.alignment(Alignment::Center)
.fg(VIVID_SKY_BLUE);
f.set_cursor(
layer_two[1].x
+ (layer_two[1].width / 2) as u16
+ (self.discord_input_filed.value().len() / 2) as u16
+ if self.discord_input_filed.value().len() % 2 != 0 {
1
} else {
0
},
layer_two[1].y,
);
f.render_widget(input, layer_two[1]);
let text = Paragraph::new(" Submit your username and track your progress on\n our Discord server. Note: your username may be\n different from your display name.");
f.render_widget(text.fg(GHOST_WHITE), layer_two[2]);
let dash = Block::new()
.borders(Borders::BOTTOM)
.border_style(Style::new().fg(GHOST_WHITE));
f.render_widget(dash, layer_two[3]);
let buttons_layer = Layout::horizontal(vec![
Constraint::Percentage(50),
Constraint::Percentage(50),
])
.split(layer_two[4]);
let button_no = Line::from(vec![Span::styled(
" No, Cancel [Esc]",
Style::default().fg(LIGHT_PERIWINKLE),
)]);
let button_yes_style = if self.discord_input_filed.value().is_empty() {
Style::default().fg(LIGHT_PERIWINKLE)
} else {
Style::default().fg(EUCALYPTUS)
};
f.render_widget(button_no, buttons_layer[0]);
let button_yes = Line::from(vec![Span::styled(
"Submit Username [Enter]",
button_yes_style,
)]);
f.render_widget(button_yes, buttons_layer[1]);
}
}
f.render_widget(pop_up_border, layer_zero);
Ok(())
}
}