Skip to main content

gm_lib/tui/app/widgets/
invite_popup.rs

1use std::{sync::mpsc, time::Duration};
2
3use crossterm::event::{KeyCode, KeyEventKind};
4use ratatui::{
5    buffer::Buffer,
6    layout::Rect,
7    widgets::{Block, Widget},
8};
9use tokio::task::JoinHandle;
10
11use crate::tui::{
12    app::{widgets::popup::Popup, SharedState},
13    traits::{CustomRender, HandleResult},
14    Event,
15};
16
17#[derive(Copy, Clone, Debug, Default, PartialEq)]
18pub enum InviteCodeValidity {
19    #[default]
20    Checking,
21    Valid,
22    Invalid,
23    Claimed,
24}
25
26#[derive(Clone, Debug, Default, PartialEq)]
27pub enum InviteCodeClaimStatus {
28    #[default]
29    Idle,
30    Claiming,
31    Success,
32    Failed(String),
33}
34
35pub fn start_check_thread(
36    invite_code: &str,
37    tr: &mpsc::Sender<Event>,
38) -> crate::Result<JoinHandle<()>> {
39    let tr = tr.clone();
40    let invite_code = invite_code.to_string();
41    Ok(tokio::spawn(async move {
42        // TODO make api call to backend to check invite code validity
43        let _ = invite_code;
44        let result = InviteCodeValidity::Valid;
45        tokio::time::sleep(Duration::from_secs(1)).await;
46
47        let _ = tr.send(Event::InviteCodeValidity(result));
48    }))
49}
50
51pub fn start_claim_thread(
52    invite_code: &str,
53    tr: &mpsc::Sender<Event>,
54) -> crate::Result<JoinHandle<()>> {
55    let tr = tr.clone();
56    let invite_code = invite_code.to_string();
57    Ok(tokio::spawn(async move {
58        let _ = tr.send(Event::InviteCodeClaimStatus(
59            InviteCodeClaimStatus::Claiming,
60        ));
61        // TODO make api call to backend to claim invite code
62        let _ = invite_code;
63        tokio::time::sleep(Duration::from_secs(1)).await;
64
65        let _ = tr.send(Event::InviteCodeClaimStatus(InviteCodeClaimStatus::Success));
66    }))
67}
68
69#[derive(Default)]
70pub struct InvitePopup {
71    invite_code: Option<String>,
72    validity: InviteCodeValidity,
73    claim_status: InviteCodeClaimStatus,
74    check_thread: Option<JoinHandle<()>>,
75    claim_thread: Option<JoinHandle<()>>,
76    open: bool,
77}
78
79impl InvitePopup {
80    pub fn is_open(&self) -> bool {
81        self.open
82    }
83
84    pub fn open(&mut self) {
85        self.open = true;
86    }
87
88    pub fn close(&mut self) {
89        self.open = false;
90    }
91
92    pub fn set_invite_code(&mut self, text: &str) {
93        self.reset();
94        self.invite_code = Some(text.to_string());
95    }
96
97    fn reset(&mut self) {
98        if let Some(thread) = self.check_thread.take() {
99            thread.abort();
100        }
101
102        if let Some(thread) = self.claim_thread.take() {
103            thread.abort();
104        }
105    }
106
107    pub fn handle_event(
108        &mut self,
109        event: &Event,
110        tr: &mpsc::Sender<Event>,
111    ) -> crate::Result<HandleResult> {
112        let mut result = HandleResult::default();
113
114        if self.check_thread.is_none() {
115            if let Some(invite_code) = self.invite_code.as_ref() {
116                let check_thread = start_check_thread(invite_code, tr)?;
117                self.check_thread = Some(check_thread);
118            }
119        }
120
121        match event {
122            Event::Input(key_event) => {
123                if key_event.kind == KeyEventKind::Press {
124                    match key_event.code {
125                        KeyCode::Enter => {
126                            if self.validity == InviteCodeValidity::Valid
127                                && self.claim_status == InviteCodeClaimStatus::Idle
128                            {
129                                if let Some(invite_code) = self.invite_code.as_ref() {
130                                    let claim_thread = start_claim_thread(invite_code, tr)?;
131                                    self.claim_thread = Some(claim_thread);
132                                }
133                            }
134                        }
135                        KeyCode::Esc => {
136                            self.close();
137                            result.esc_ignores = 1;
138                        }
139                        _ => {}
140                    }
141                }
142            }
143            Event::InviteCodeValidity(validity) => {
144                self.validity = *validity;
145            }
146            Event::InviteCodeClaimStatus(status) => {
147                self.claim_status = status.clone();
148            }
149            _ => {}
150        }
151        result.esc_ignores = 1;
152        Ok(result)
153    }
154
155    pub fn render(&self, area: Rect, buf: &mut Buffer, shared_state: &SharedState)
156    where
157        Self: Sized,
158    {
159        if self.is_open() {
160            // if wallet address exists
161            // we will start invite process
162            // if wallet address does not exist
163            // we will suggest user to create a new account, even show a button - "go to create new account"
164            let theme = shared_state.theme.popup();
165
166            Popup.render(area, buf, &theme);
167
168            let inner_area = Popup::inner_area(area);
169            let block = Block::bordered();
170            let block_inner_area = block.inner(inner_area);
171            block.render(inner_area, buf);
172
173            let area = block_inner_area;
174
175            [
176                "Welcome! And thanks for joining gm's alpha testing program!".to_string(),
177                if let Some(invite_code) = self.invite_code.as_ref() {
178                    match self.validity {
179                        InviteCodeValidity::Checking => {
180                            format!("Invite Code: \"{invite_code}\", checking validity...")
181                        }
182                        InviteCodeValidity::Valid => {
183                            format!("Invite code: \"{invite_code}\", valid!")
184                        }
185                        InviteCodeValidity::Invalid => {
186                            format!(
187                                "Invite Code: \"{invite_code}\" is invalid, please check the code"
188                            )
189                        }
190                        InviteCodeValidity::Claimed => {
191                            format!("Invite Code: \"{invite_code}\", claimed already")
192                        }
193                    }
194                } else {
195                    "Invite Code not provided, this should not happen".to_string()
196                },
197                match self.claim_status {
198                    InviteCodeClaimStatus::Idle => {
199                        if self.validity == InviteCodeValidity::Valid {
200                            "Press Enter to claim".to_string()
201                        } else {
202                            "".to_string()
203                        }
204                    }
205                    InviteCodeClaimStatus::Claiming => "Claiming invite code...".to_string(),
206                    InviteCodeClaimStatus::Success => "Claimed successfully!".to_string(),
207                    InviteCodeClaimStatus::Failed(ref msg) => {
208                        format!("Failed to claim invite code: {msg}")
209                    }
210                },
211            ]
212            .render(area, buf, true);
213        }
214    }
215}