-
Notifications
You must be signed in to change notification settings - Fork 3
/
Popup.jsx
369 lines (337 loc) · 11.5 KB
/
Popup.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
// Popup.jsx is the popup window for our extension.
//
// The popup window shows a form where a user can input
// the symbol and quantity of a given cryptocurrency.
// A user can dynamically add or delete the # of cryptocurrencies
// to enter in the form. The minimum # is 1.
//
// The form can only be submitted if all the inputs are valid,
// meaning that all the coin names match an item in the coinsSupportedOnBinance
// array (which is populated by fetching Binance API for a list of all coins
// supported by the Binance API) and all the coin quantity are valid numbers greater than 0.
//
// Once the form is submitted, a message is sent to the content script
// with an array of all the coin objects. If no response is received from the
// content script, popup will display an error and tell the user that they
// must be logged into Mint.com.
//
// If popup sends a message to the content script while user is on the Mint.com
// homepage but NOT Logged in, the content script will receive the message
// but send a response of {loggedIn: false}. popup will display an error and
// tell the user that they must be logged into Mint.com.
//
// The content script will then calculate the value of all the coins
// using price data from the Binance API, then either create or modify a
// cryptocurrency property in Mint.
import React from "react";
import CoinInputs from "./CoinInputs";
import "./Popup.css";
import InputLabel from "@material-ui/core/InputLabel";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles";
let coinsSupportedOnBinance = [];
class Popup extends React.Component {
// State is:
// 1) an array of coin objects.
// 2) a string for error state. There are three error states: none, notOnMintURL, & onMintURLButNotLoggedIn
state = {
coins: [
{
coinName: "",
coinQuantity: "",
isCoinNameValid: false,
isCoinQuantityValid: false,
hasFormBeenSubmitted: false,
},
],
error: "none",
userCurrencySymbol: "USD",
};
// theme for currency select dropdown (Materials UI) to use
theme = createMuiTheme({
typography: {
fontFamily: "Heebo, sans-serif",
},
palette: {
primary: {
main: "#06B6C9",
},
},
});
// After the component mounts, set state.coin to the data in chrome.storage (if the data exists).
// When a user successfully submits the popup form, the data (coin names and quantities)
// is stored in chrome.storage.
// Also, fetch exchange info from Binance API. We get a JSON response that will
// tell us all the cryptocurrency symbols currently supported by Binance.
componentDidMount() {
// Get user's coin data from Chrome storage
chrome.storage.sync.get("userSubmittedCoinData", (data) => {
if (data.userSubmittedCoinData) {
this.setState({ coins: data.userSubmittedCoinData });
}
});
chrome.storage.sync.get("userCurrencySymbol", (data) => {
if (data.userCurrencySymbol) {
this.setState({
userCurrencySymbol: data.userCurrencySymbol,
});
} else {
this.setState({
userCurrencySymbol: "USD",
});
}
});
// Fetch list of supported coins from Binance API
fetch(`https://api.binance.com/api/v1/exchangeInfo`)
.then((res) => res.json())
.then((json) => this.createArrayOfCoinsSupportedOnBinance(json));
}
// Store all coin symbols supported by Binance in an array.
// We will check any user-inputted coin names against this array
createArrayOfCoinsSupportedOnBinance(json) {
for (let i = 0; i < json.symbols.length; i++) {
coinsSupportedOnBinance[i] = json.symbols[i].baseAsset;
}
}
// This function is entered whenever user types into the form.
// Update the state with user's input.
// Also check if user's input is valid-- coin name has to match
// an element in our array of coins supported on Binance, and coin
// quantities have to be a valid positive number.
// The form cannot be submitted unless all inputs are valid. If form is
// submitted with any invalid inputs, those inputs will display an error
// indicator (red line).
handleChangeForm = (e) => {
// Ignore spaces, numbers typed into a "name" input, and letters into a "quantity" input
if (this.isUserTypingSpaces(e) | this.isUserTypingNumbersIntoNameField(e) | this.isUserTypingLettersIntoQuantityField(e)) {
return;
}
this.updateCoinState(e);
};
isUserTypingSpaces = (e) => {
if (e.nativeEvent.data == " ") {
return true;
}
return false;
};
isUserTypingNumbersIntoNameField = (e) => {
if (["coinName"].includes(e.target.dataset.fieldType)) {
let reg = new RegExp("[0-9]");
if (reg.test(e.nativeEvent.data)) {
if (e.nativeEvent.data != null) {
return true;
}
}
}
return false;
};
isUserTypingLettersIntoQuantityField = (e) => {
if (["coinQuantity"].includes(e.target.dataset.fieldType)) {
let reg = new RegExp("[a-zA-Z]");
if (reg.test(e.nativeEvent.data)) {
if (e.nativeEvent.data != null) {
return true;
}
}
}
return false;
};
// Update state.coin
updateCoinState = (e) => {
if (["coinName", "coinQuantity"].includes(e.target.dataset.fieldType)) {
let coins = [...this.state.coins];
coins[e.target.dataset.id][e.target.dataset.fieldType] = e.target.value.toUpperCase();
// Input field is a coin name
// Check if coin name is valid (has to match a symbol in coinList array)
if (["coinName"].includes(e.target.dataset.fieldType)) {
if (this.checkIfCoinNameIsValid(e, coins)) {
coins[e.target.dataset.id].isCoinNameValid = true;
} else {
coins[e.target.dataset.id].isCoinNameValid = false;
}
}
// Input field is a coin quantity
// Check if input is valid (valid number AND greater than 0)
if (["coinQuantity"].includes(e.target.dataset.fieldType)) {
if (this.checkIfCoinQuantityIsValid(e, coins)) {
coins[e.target.dataset.id].isCoinQuantityValid = true;
} else {
coins[e.target.dataset.id].isCoinQuantityValid = false;
}
}
this.setState({ coins });
} else {
this.setState({ [e.target.coinName]: e.target.value.toUpperCase() });
}
};
checkIfCoinNameIsValid = (e, coins) => {
if (coinsSupportedOnBinance.includes(coins[e.target.dataset.id][e.target.dataset.fieldType])) {
return true;
} else {
return false;
}
};
checkIfCoinQuantityIsValid = (e, coins) => {
if (!isNaN(coins[e.target.dataset.id].coinQuantity) && coins[e.target.dataset.id].coinQuantity > 0) {
return true;
} else {
return false;
}
};
handleChangeCurrencySymbol = (e) => {
e.preventDefault();
this.setState({
userCurrencySymbol: e.target.value,
});
};
// This function is entered whenever user submits the form.
handleSubmitForm = (e) => {
e.preventDefault();
// Set coins.hasFormBeenSubmitted to true for all coins. This is used by <CoinInputs> component
// to set error indicators (red color) to the invalid input fields on a form submit
this.setSubmitToTrueForAllCoins();
// Determine if all fields contain valid inputs (valid coin name quantity)
let allInputsAreValid = this.state.coins.reduce((sum, next) => sum && next.isCoinNameValid && next.isCoinQuantityValid, true);
if (allInputsAreValid) {
chrome.runtime.sendMessage({ message: "popupSync" }); // tell background script that we are about to run a sync => so ignore any URL changes for 1 minute
setTimeout(() => {
// Send data to the content script via chrome.messaging
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { coinData: this.state.coins }, (response) => {
// content script did not send a response = user is not on a mint.intuit.com/* URL
// do nothing besides change state.error
if (response == undefined) {
this.setState({
error: "notOnMintURL",
});
}
// content script sent a response but user is NOT logged into Mint so content script
// cannot do its job
else if (response.loggedIn == false) {
this.setState({
error: "onMintURLButNotLoggedIn",
});
}
// content script sent a response. user is logged into Mint.com.
// content script will take it from here
// save user's coin data to Chrome Storage and close pop-up window.
else {
this.setState({
error: "none",
});
this.saveDataToChromeStorage();
this.closePopUpWindow();
}
});
});
}, 250);
}
};
setSubmitToTrueForAllCoins() {
let coins = [...this.state.coins];
for (let i = 0; i < this.state.coins.length; i++) {
coins[i].hasFormBeenSubmitted = true;
}
this.setState({ coins });
}
closePopUpWindow() {
window.close();
}
saveDataToChromeStorage() {
chrome.storage.sync.set({ userSubmittedCoinData: this.state.coins, userCurrencySymbol: this.state.userCurrencySymbol }, function () {
//console.log("Settings saved");
});
}
// Add a new coin object
addCoin() {
this.setState((prevState) => ({
coins: [
...prevState.coins,
{
coinName: "",
coinQuantity: "",
isCoinNameValid: false,
isCoinQuantityValid: false,
hasFormBeenSubmitted: false,
},
],
}));
}
// Delete a specified coin object
deleteCoin(index) {
if (this.state.coins.length > 1) {
let tempCoins = [...this.state.coins];
tempCoins.splice(index, 1);
this.setState({
coins: tempCoins,
});
}
}
render() {
let deleteCoin = this.deleteCoin;
let addCoin = this.addCoin;
let { coins } = this.state;
let error = this.state.error;
let submitButtonText;
let submitButtonClass = "submit";
// Change submit button text based on error state
switch (error) {
case "notOnMintURL":
submitButtonText = "Error! - Please log into Mint.com (If you are logged into Mint.com, please refresh the page)";
submitButtonClass = "submit error";
break;
case "onMintURLButNotLoggedIn":
submitButtonText = "Error! - Please log into Mint.com";
submitButtonClass = "submit error";
break;
// no error
default:
submitButtonText = "Sync to Mint";
}
return (
<div className='popUpContainer'>
<div className='headerContainer'>
<h1>Mint Crypto</h1>
</div>
<div className='formContainer'>
<form onSubmit={this.handleSubmitForm} onChange={this.handleChangeForm} className='myForm'>
<CoinInputs coins={coins} deleteCoin={deleteCoin.bind(this)} addCoin={addCoin.bind(this)} />
</form>
<div className='submitContainer'>
<input
onClick={this.handleSubmitForm}
type='submit'
value={submitButtonText}
className={submitButtonClass}
/>
</div>
<ThemeProvider theme={this.theme}>
<InputLabel id='currency-symbol-dropdown'>
Currency of your Mint account (Mint Crypto will calculate the value of your crypto in this
currency)
</InputLabel>
<Select
labelId='currency-symbol-select-label'
id='currency-symbol-select'
value={this.state.userCurrencySymbol}
onChange={this.handleChangeCurrencySymbol}
>
<MenuItem value={"USD"}>USD</MenuItem>
<MenuItem value={"CAD"}>CAD</MenuItem>
<MenuItem value={"AUD"}>AUD</MenuItem>
<MenuItem value={"EUR"}>EUR</MenuItem>
<MenuItem value={"INR"}>INR</MenuItem>
<MenuItem value={"GBP"}>GBP</MenuItem>
<MenuItem value={"CNY"}>CNY</MenuItem>
<MenuItem value={"JPY"}>JPY</MenuItem>
<MenuItem value={"MXN"}>MXN </MenuItem>
<MenuItem value={"CHF"}>CHF</MenuItem>
</Select>
</ThemeProvider>
</div>
</div>
);
}
}
export default Popup;