diff --git a/src/index.html b/src/index.html index effdaba..f596e0f 100644 --- a/src/index.html +++ b/src/index.html @@ -9,7 +9,7 @@ @@socialMediaTags @@ -80,8 +80,13 @@

-:-

+
+ + +
+

- Bitcoin price is + Crypto price is ...

@@ -123,6 +128,7 @@

Open Source | API +

@@ -145,10 +151,11 @@

+ - + diff --git a/src/js/api.js b/src/js/api.js index d31a934..aa03b9f 100644 --- a/src/js/api.js +++ b/src/js/api.js @@ -50,4 +50,4 @@ class API { // window.App.API = new API(App.apiFakeAdapter); //window.App.API = new API(App.apiGoranAdapter); -window.App.API = new API(App.apiCecoAdapter); +window.App.API = new API(App.apiBoyoAdapter); diff --git a/src/js/apiBoyoAdapter.js b/src/js/apiBoyoAdapter.js new file mode 100644 index 0000000..b88af06 --- /dev/null +++ b/src/js/apiBoyoAdapter.js @@ -0,0 +1,28 @@ +window.App.apiBoyoAdapter = { + mapData: function (response, dateLabelFormat) { + return response + .map((_rec) => ({ + value: _rec.value, + timestamp: dayjs + .utc(_rec.timestamp * 1000) + .local() + .format(dateLabelFormat), + })) + .reverse(); + }, + + getCryptoRatesForPeriod: function (period, cryptoType) { + return new Promise((resolve, reject) => { + chrome.runtime.sendMessage( + { type: 'getCryptoPrice', period: period, cryptoType: cryptoType }, + (response) => { + if (response && !response.error) { + resolve(response.data); + } else { + reject(response.error || `Failed to retrieve ${cryptoType} price data`); + } + } + ); + }); + }, +}; diff --git a/src/js/background.js b/src/js/background.js index 92f9518..639873b 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -1,30 +1,33 @@ -let bitcoinPriceData = {}; +let cryptoPriceData = { + bitcoin: {}, + ethereum: {}, +}; -async function fetchBitcoinPrice(period) { +async function fetchCryptoPrice(period, cryptoType) { const endpoints = { - ALL: 'bitcoin/all', - ONE_YEAR: 'bitcoin/year', - ONE_MONTH: 'bitcoin/month', - ONE_WEEK: 'bitcoin/week', - ONE_DAY: 'bitcoin/day', - ONE_HOUR: 'bitcoin/hour', - NOW: 'bitcoin/now', + ALL: `${cryptoType}/all`, + ONE_YEAR: `${cryptoType}/year`, + ONE_MONTH: `${cryptoType}/month`, + ONE_WEEK: `${cryptoType}/week`, + ONE_DAY: `${cryptoType}/day`, + ONE_HOUR: `${cryptoType}/hour`, + NOW: `${cryptoType}/now`, }; try { - const response = await fetch(`https://api.crypto-tab.com/v1/${endpoints[period]}`); + const response = await fetch(`http://localhost:3000/v1/${endpoints[period]}`); // TODO: Update to use the deployed API (maybe use a config file to store the base URL) const data = await response.json(); - bitcoinPriceData[period] = data; + cryptoPriceData[cryptoType][period] = data; } catch (error) { - console.error(`Error fetching Bitcoin price for ${period}:`, error); + console.error(`Error fetching ${cryptoType} price for ${period}:`, error); } } chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - if (request.type === 'getBitcoinPrice') { - const period = request.period; - fetchBitcoinPrice(period).then(() => { - sendResponse({ data: bitcoinPriceData[period], cached: false }); + if (request.type === 'getCryptoPrice') { + const { period, cryptoType } = request; + fetchCryptoPrice(period, cryptoType).then(() => { + sendResponse({ data: cryptoPriceData[cryptoType][period], cached: false }); }); return true; // Indicates that the response will be sent asynchronously } diff --git a/src/js/bitcoin.js b/src/js/crypto.js similarity index 57% rename from src/js/bitcoin.js rename to src/js/crypto.js index e04e9a2..6b91890 100644 --- a/src/js/bitcoin.js +++ b/src/js/crypto.js @@ -5,7 +5,7 @@ dayjs.extend(window.dayjs_plugin_calendar); window.App = window.App || {}; -window.App.Bitcoin = { +window.App.Crypto = { PERIODS: { ONE_HOUR: 'ONE_HOUR', ONE_DAY: 'ONE_DAY', @@ -21,6 +21,10 @@ window.App.Bitcoin = { chart: null, $dataPeriods: document.querySelectorAll('.js-period'), + $cryptoToggle: document.querySelectorAll('input[name="crypto"]'), + $cryptoTypeLabel: document.getElementById('crypto-type'), + currentCrypto: '', + initEvents() { const self = this; @@ -30,33 +34,64 @@ window.App.Bitcoin = { this.classList.add('active'); const period = this.dataset.period; - self.getBitcoinData(period) + self.getCryptoData(period, self.currentCrypto) .then((_data) => self.chart.init(_data)) .catch((error) => { - self.handleChartRejection(period, error); + self.handleChartRejection(period, self.currentCrypto, error); }); App.Settings.set('period', this.dataset.period); - self.setPriceChange(); + self.setPriceChange(self.currentCrypto); + }); + }); + + [...self.$cryptoToggle].forEach((el) => { + el.addEventListener('change', function () { + if (this.checked) { + self.currentCrypto = this.value; + self.updateCryptoTypeLabel(self.currentCrypto); + const period = document.querySelector('.js-period.active').dataset.period; + self.getCryptoData(period, self.currentCrypto) + .then((_data) => self.chart.init(_data)) + .catch((error) => { + self.handleChartRejection(period, self.currentCrypto, error); + }); + + App.Settings.set('cryptoType', self.currentCrypto); + + self.setPriceChange(self.currentCrypto); + self.setLastUpdated(); + self.displayPriceNow(); + } }); }); - App.Settings.get().then(({ period }) => { + App.Settings.get().then(({ period, cryptoType }) => { + self.currentCrypto = + cryptoType || document.querySelector('input[name="crypto"]:checked').value; const selectedTab = period ? Object.keys(this.PERIODS).indexOf(period) : 1; + document.querySelector(`input[name="crypto"][value="${self.currentCrypto}"]`).checked = + true; + self.initRepositories(); self.$dataPeriods[selectedTab].click(); }); }, - getBitcoinData(period) { + updateCryptoTypeLabel(cryptoType) { + const label = cryptoType.charAt(0).toUpperCase() + cryptoType.slice(1); + this.$cryptoTypeLabel.textContent = label; + }, + + getCryptoData(period, cryptoType) { return new Promise((resolve, reject) => { - this.repositories[period] + this.repositories[cryptoType][period] .getData() .then((response) => { resolve(response); }) .catch((error) => { - reject(error || 'Failed to retrieve Bitcoin price data'); + reject(error || `Failed to retrieve ${cryptoType} price data`); }); }); }, @@ -78,19 +113,19 @@ window.App.Bitcoin = { } }, - handleChartRejection(_period, _error) { + handleChartRejection(_period, _cryptoType, _error) { this.isLocalChartDataOld = true; - this.repositories[_period].getDataUpToDateStatus().then((_res) => { + this.repositories[_cryptoType][_period].getDataUpToDateStatus().then((_res) => { App.Loader.destroy(); if (_res.localData === null) { - App.Message.fireError("That's extremely sad. " + _error); + App.Message.fireError(`That's extremely sad. ${_error}`); this.chart.destroy(); } else if (_res.localData.length) { App.Message.clear(); this.chart.init(_res.localData); - this.setLastUpdated(true); + this.setLastUpdated(); } }); }, @@ -104,58 +139,61 @@ window.App.Bitcoin = { const storageSetting = App.ENV.platform === 'EXTENSION' ? 'BROWSER_STORAGE' : 'LOCAL_STORAGE'; - Object.keys(this.PERIODS).forEach((period) => { - this.repositories[period] = new SuperRepo({ + ['bitcoin', 'ethereum'].forEach((cryptoType) => { + this.repositories[cryptoType] = {}; + Object.keys(this.PERIODS).forEach((period) => { + this.repositories[cryptoType][period] = new SuperRepo({ + storage: storageSetting, + name: `${cryptoType}-${period}`, + outOfDateAfter: 15 * 60 * 1000, // 15 minutes + mapData: (r) => App.API.mapData(r, this.getLabelFormat(period)), + request: () => + this.getCryptoDataFromBackground(period, cryptoType) + .then((res) => { + this.isLocalChartDataOld = false; + return res; + }) + .catch((jqXHR, textStatus, errorThrown) => { + this.handleChartRejection(period, cryptoType, jqXHR); + }), + }); + }); + + this.repositories[cryptoType]['NOW'] = new SuperRepo({ storage: storageSetting, - name: 'bitcoin-' + period, - outOfDateAfter: 15 * 60 * 1000, // 15 minutes - mapData: (r) => App.API.mapData(r, this.getLabelFormat(period)), + name: `${cryptoType}-NOW`, + outOfDateAfter: 3 * 60 * 1000, // 3 minutes + mapData: (data) => { + const { value, changePercent } = data[0]; + const { dayAgo, weekAgo, monthAgo } = changePercent; + + return { + price: value, + changePercent: { dayAgo, weekAgo, monthAgo }, + }; + }, request: () => - this.getBitcoinDataFromBackground(period) + this.getCryptoDataFromBackground('NOW', cryptoType) .then((res) => { - this.isLocalChartDataOld = false; + this.isLocalNowDataOld = false; return res; }) - .catch((jqXHR, textStatus, errorThrown) => { - this.handleChartRejection(period, jqXHR); + .catch(() => { + this.handleNowRejection(); }), }); }); - - this.repositories['NOW'] = new SuperRepo({ - storage: storageSetting, - name: 'bitcoin-NOW', - outOfDateAfter: 3 * 60 * 1000, // 3 minutes - mapData: (data) => { - const { value, changePercent } = data[0]; - const { dayAgo, weekAgo, monthAgo } = changePercent; - - return { - price: value, - changePercent: { dayAgo, weekAgo, monthAgo }, - }; - }, - request: () => - this.getBitcoinDataFromBackground('NOW') - .then((res) => { - this.isLocalNowDataOld = false; - return res; - }) - .catch(() => { - this.handleNowRejection(); - }), - }); }, - getBitcoinDataFromBackground(period) { + getCryptoDataFromBackground(period, cryptoType) { return new Promise((resolve, reject) => { window.browser.runtime.sendMessage( - { type: 'getBitcoinPrice', period: period }, + { type: 'getCryptoPrice', period: period, cryptoType: cryptoType }, (response) => { if (response && !response.error) { resolve(response.data); } else { - reject(response.error || 'Failed to retrieve Bitcoin price data'); + reject(response.error || `Failed to retrieve ${cryptoType} price data`); } } ); @@ -168,8 +206,8 @@ window.App.Bitcoin = { }, $change: document.querySelector('#change'), - async setPriceChange() { - let { localData } = await this.repositories['NOW'].getDataUpToDateStatus(); + async setPriceChange(cryptoType) { + let { localData } = await this.repositories[cryptoType]['NOW'].getDataUpToDateStatus(); if (!localData) { return; } @@ -234,7 +272,8 @@ window.App.Bitcoin = { $lastUpdated: document.querySelector('#last-updated'), setLastUpdated() { - this.repositories['NOW'].getDataUpToDateStatus().then((info) => { + const cryptoType = this.currentCrypto; + this.repositories[cryptoType]['NOW'].getDataUpToDateStatus().then((info) => { const prettyLastUpdatedTime = dayjs(info.lastFetched).fromNow(); // Clear existing content @@ -247,8 +286,8 @@ window.App.Bitcoin = { const failureMessage = this.isLocalChartDataOld || this.isLocalNowDataOld - ? '. Data request failed. Refresh the page to try again.' - : '.'; + ? `. Data request failed. Refresh the page to try again.` + : `.`; this.$lastUpdated.appendChild(lastUpdatedSpan); this.$lastUpdated.appendChild(document.createTextNode(failureMessage)); @@ -258,15 +297,16 @@ window.App.Bitcoin = { }, displayPriceNow() { - this.repositories['NOW'] + const cryptoType = this.currentCrypto; + this.repositories[cryptoType]['NOW'] .getData() .then((_data) => { - this.setPriceChange(); + this.setPriceChange(cryptoType); this.setLastUpdated(); }) .catch(() => { this.handleNowRejection(); - this.setPriceChange(); + this.setPriceChange(cryptoType); this.setLastUpdated(); }); @@ -278,8 +318,13 @@ window.App.Bitcoin = { this.chart = new App.Chart(this.$chart); this.initRepositories(); - this.displayPriceNow(); - this.initEvents(); + + App.Settings.get().then(({ cryptoType }) => { + this.currentCrypto = + cryptoType || document.querySelector('input[name="crypto"]:checked').value; + this.updateCryptoTypeLabel(this.currentCrypto); + this.displayPriceNow(); + }); }, }; diff --git a/src/js/script.js b/src/js/script.js index 1dc6cb5..7e37ac4 100644 --- a/src/js/script.js +++ b/src/js/script.js @@ -1,10 +1,10 @@ -window.App.Bitcoin.init(); +window.App.Crypto.init(); window.onload = () => { const { platform } = App.ENV; // Display platform specific DOM elements - [...document.querySelectorAll(`[data-platform="${platform}"]`)].forEach( el => { + [...document.querySelectorAll(`[data-platform="${platform}"]`)].forEach((el) => { el.classList.remove('hidden'); });