diff --git a/company.html b/company.html new file mode 100644 index 0000000..74d46be --- /dev/null +++ b/company.html @@ -0,0 +1,49 @@ + + + + + + + + + + Company Info + + + + +

+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..8a66f08 --- /dev/null +++ b/css/style.css @@ -0,0 +1,106 @@ +html { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; +} +body { + margin: 0px; + height: 1vh; + color: white; +} + +html { + background-color: #2b2b2b; + color: white; + border: none; +} +a, +li, +ul, +h1 { + color: white !important; + text-decoration: none !important; + font-weight: 400; +} + +input { + background-color: #2b2b2b; + color: white; + border: none; +} +button { + margin-top: 10px !important; + background-color: #2b2b2b; + border: none; + color: white; +} +p:not() { + color: white; + display: none; +} +#loading { +} +input { + border: 1px white solid; + border-radius: 10px; +} +#searchDiv { + background-color: #ffd829; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} +.text-success:after { + content: ""; + display: inline-block; + width: 0; + height: 0; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 5px solid green; + margin-left: 5px; + transform: rotate(42deg); +} +.text-danger:after { + content: ""; + display: inline-block; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 6px solid red; + margin-left: 5px; + transform: rotate(4deg); +} +#chartContainer { + height: 500px; + width: 500px; +} +.ticker-container { + overflow: hidden; + white-space: nowrap; + height: 15px; +} + +.ticker-list { + display: inline-block; + margin: 0; + padding: 0; + animation: ticker 10000s linear infinite; +} + +@keyframes ticker { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-100%); + } +} + +@media (max-width: 767px) { + #searchDiv { + height: 10em; + } +} diff --git a/favicon.png b/favicon.png new file mode 100644 index 0000000..27ac658 Binary files /dev/null and b/favicon.png differ diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000..f87dcf6 Binary files /dev/null and b/images/logo.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..5d7c18b --- /dev/null +++ b/index.html @@ -0,0 +1,54 @@ + + + + + + + + + + .dotstocks.com + + + +
+
+
+

.your_search_results:

+

loading...

+
+
+

.search_NASDAQ_stocks:

+ + +
+
+
+ +
+ + + + + + diff --git a/js/company.js b/js/company.js new file mode 100644 index 0000000..12d496e --- /dev/null +++ b/js/company.js @@ -0,0 +1,99 @@ +const queryParamOfThePage = new URLSearchParams(window.location.search).get( + "symbol" +); +const endPointLink = `https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3/company/profile/${queryParamOfThePage}`; +const chartEndPointLink = `https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3/historical-price-full/${queryParamOfThePage}?serietype=line`; + +const infoDiv = document.getElementById("infoDiv"); +const companyNameDiv = document.getElementById("companyName"); +const companyUrlDiv = document.getElementById("companyUrl"); +const companyDescriptionDiv = document.getElementById("companyDescription"); +const companyPriceDiv = document.getElementById("companyPrice"); +const companyChangeDiv = document.getElementById("companyChanges"); +const chartDraw = document.getElementById("myChart").getContext("2d"); + +const h1 = document.getElementById("h1"); +h1.textContent = `Info about ${queryParamOfThePage}`; + +fetch(endPointLink) + .then((response) => response.json()) + .then((data) => { + let changesNegPoz = + data.profile.changesPercentage > 0 ? "text-success" : "text-danger"; + companyNameDiv.innerHTML = `Company Name: ${data.profile.companyName}`; + companyPriceDiv.innerHTML = `Price: ${data.profile.price}$`; + companyChangeDiv.innerHTML = `Latest Changes: ${data.profile.changesPercentage}`; + companyUrlDiv.innerHTML = `URL: ${data.profile.website}`; + companyDescriptionDiv.innerHTML = `Description: ${data.profile.description}`; + const logoImg = document.createElement("img"); + logoImg.src = `${data.profile.image}`; + logoImg.alt = "Company Logo"; + logoImg.width = 100; + logoImg.height = 100; + logoImg.style.float = "right"; + infoDiv.appendChild(logoImg); + }) + .catch((error) => console.error(error)); + +async function loadData() { + try { + const response = await fetch(chartEndPointLink); + const data = await response.json(); + const prices = data.historical.map((d) => d.close); + const dates = data.historical.map((d) => d.date); + const chart = new Chart(chartDraw, { + type: "bar", + data: { + labels: dates, + datasets: [ + { + label: "Close", + data: prices, + backgroundColor: "rgba(255,99,132,0.2)", + borderColor: "rgba(255,99,132,1)", + borderwidth: 0.5, + borderWidth: 1, + pointRadius: 2, + pointHoverRadius: 4, + }, + ], + }, + options: { + scales: { + y: { + beginAtZero: true, + }, + }, + }, + }); + } catch (error) { + console.error(error); + } +} + +loadData(); + +const tickerList = document.querySelector(".ticker-list"); +function MarqueeList() { + const tickerEndpoint = + "https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3/quotes/nyse?exchange=NASDAQ"; + fetch(tickerEndpoint) + .then((response) => response.json()) + .then((data) => { + data.forEach((item) => { + const changesNegPoz = + item.changesPercentage > 0 ? "text-success" : "text-danger"; + const listItem = document.createElement("li"); + listItem.innerHTML = ` + ${item.symbol}: ${item.price} (${item.changesPercentage}%) + + `; + tickerList.appendChild(listItem); + }); + }) + .catch((error) => { + console.error("Error fetching ticker data:", error); + }); +} + +MarqueeList(); diff --git a/js/marquee.js b/js/marquee.js new file mode 100644 index 0000000..127197f --- /dev/null +++ b/js/marquee.js @@ -0,0 +1,33 @@ +class MarqueeList { + constructor() { + this.tickerList = document.querySelector(".ticker-list"); + this.tickerEndpoint = + "https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3/quotes/nyse?exchange=NASDAQ"; + this.fetchData(); + } + + fetchData() { + fetch(this.tickerEndpoint) + .then((response) => response.json()) + .then((data) => { + this.renderData(data); + }) + .catch((error) => { + console.error("Error fetching ticker data:", error); + }); + } + + renderData(data) { + data.forEach((item) => { + const changesNegPoz = + item.changesPercentage > 0 ? "text-success" : "text-danger"; + const listItem = document.createElement("li"); + listItem.innerHTML = ` + ${item.symbol}: ${item.price} (${item.changesPercentage}%) + `; + this.tickerList.appendChild(listItem); + }); + } +} + +new MarqueeList(); diff --git a/js/script.js b/js/script.js new file mode 100644 index 0000000..d589ddb --- /dev/null +++ b/js/script.js @@ -0,0 +1,113 @@ +let searchBoxValue = document.getElementById("searchBox"); +const searchButton = document.getElementById("searchButton"); +const resultsDiv = document.getElementById("resultsDiv"); +const loaderP = document.getElementById("loading"); +const baseUrl = + "https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3"; + +document.addEventListener("DOMContentLoaded", function (event) { + document.querySelectorAll("img").forEach(function (img) { + img.onerror = function () { + this.style.display = "none"; + }; + }); +}); + +showLoader = () => { + loaderP.style.display = "unset"; +}; + +hideLoader = () => { + loaderP.style.display = "none"; +}; +queryAddress = () => { + window.location.search = `query=${searchBoxValue.value}`; +}; +let timeout = 0; + +function getNameAndSymbol(name) { + let endpoint = + "https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3/search?query=" + + name + + "&limit=10&exchange=NASDAQ"; + resultsDiv.innerHTML = "

.your_search_results:

"; + return fetch(endpoint) + .then((response) => response.json()) + .then((data) => { + let promises = []; + for (let i = 0; i < 10; i++) { + let symbol = data[i].symbol; + let imageUrl = ""; + let promise = fetch( + `https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3/company/profile/${data[i].symbol}` + ) + .then((response) => response.json()) + .then((data) => { + let changesNegPoz = + data.profile.changesPercentage > 0 + ? "text-success" + : "text-danger"; + const pEls = resultsDiv.querySelectorAll("div p"); + pEls[i].textContent = `Change: ${data.profile.changesPercentage}%`; + pEls[i].classList.add(changesNegPoz); + if (data.profile.changesPercentage > 0) { + pEls[i].innerHTML += ` `; + } else if (data.profile.changesPercentage < 0) { + pEls[i].innerHTML += ` `; + } + imageUrl = data.profile.image; + + const imgEl = resultsDiv.querySelector( + `img[data-symbol="${symbol}"]` + ); + if (imgEl) { + if (imageUrl) { + imgEl.src = imageUrl; + } else { + return; + } + } + const firstPEl = resultsDiv.querySelectorAll("p"); + if (firstPEl) { + firstPEl.textContent = `Changes Percentage: ${data.profile.changesPercentage}%`; + firstPEl.classList.add(changesNegPoz); + const triangleIcon = document.createElement("span"); + triangleIcon.classList.add("triangle"); + triangleIcon.classList.add( + data.profile.changesPercentage > 0 ? "positive" : "negative" + ); + firstPEl.appendChild(triangleIcon); + } + }) + .catch((error) => (imgEl.style.display = "none")); //how to bring it to work ); + promises.push(promise); + resultsDiv.innerHTML += ` +
+ + ${data[i].name}, (${symbol}) + ${data[i].name} icon +

+
+
+ `; + } + return Promise.all(promises); + }) + .catch((error) => console.log(error)); +} + +autoSearchDebounce = () => { + clearTimeout(timeout); + timeout = setTimeout(async () => { + showLoader(); + await getNameAndSymbol(searchBoxValue.value); + hideLoader(); + }, 500); +}; + +bothFunctions = () => { + autoSearchDebounce(); +}; + +searchButton.addEventListener("click", autoSearchDebounce, queryAddress); +searchBoxValue.addEventListener("keyup", autoSearchDebounce); diff --git a/js/script_milestone1.js b/js/script_milestone1.js new file mode 100644 index 0000000..c95185d --- /dev/null +++ b/js/script_milestone1.js @@ -0,0 +1,41 @@ +let searchBoxValue = document.getElementById("searchBox"); +const searchButton = document.getElementById("searchButton"); +const resultsDiv = document.getElementById("resultsDiv"); +showLoader = () => { + document.getElementById("loading").style.display = "unset"; +}; +hideloader = () => { + document.getElementById("loading").style.display = "none"; +}; +endpointFunction = () => { + showLoader(); + let endpoint = + "https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3/search?query=" + + searchBoxValue.value + + "&limit=10&exchange=NASDAQ"; + async function getNameAndSymbol() { + await fetch(endpoint) + .then((response) => response.json()) + .then((data) => { + console.log(data); + console.log(data.length); + resultsDiv.innerHTML = ""; + for (let i = 0; i < 10; i++) { + resultsDiv.innerHTML += `${data[i].name}, (${data[i].symbol})

`; + } + }) + .catch((error) => console.log("error")); + } + hideloader(); + getNameAndSymbol(); +}; + +searchButton.addEventListener("click", endpointFunction); + +/*Questions! +1. Why isn't searchButton.addEventListener working while being in the beginning of the page? +2. How can I make endpointFunction more lovely, what can I get rid of? +3. How to work with showLoader and hideloader functions in a better way? + + +*/ diff --git a/js/searchForm.js b/js/searchForm.js new file mode 100644 index 0000000..998862a --- /dev/null +++ b/js/searchForm.js @@ -0,0 +1,43 @@ +class SearchForm { + constructor(searchBox, searchButton, loader) { + this.searchBox = searchBox; + this.searchButton = searchButton; + this.loader = loader; + this.timeout = 0; + + this.searchButton.addEventListener("click", () => { + this.autoSearchDebounce(); + this.queryAddress(); + }); + this.searchBox.addEventListener("keyup", () => this.autoSearchDebounce()); + } + + showLoader() { + this.loader.style.display = "unset"; + } + + hideLoader() { + this.loader.style.display = "none"; + } + + queryAddress() { + window.location.search = `query=${this.searchBox.value}`; + } + + autoSearchDebounce() { + clearTimeout(this.timeout); + this.timeout = setTimeout(async () => { + this.showLoader(); + await searchResults.getNameAndSymbol(this.searchBox.value); + this.hideLoader(); + }, 500); + } +} + +document.addEventListener("DOMContentLoaded", () => { + const searchBox = document.getElementById("searchBox"); + const searchButton = document.getElementById("searchButton"); + const loader = document.getElementById("loading"); + + new SearchForm(searchBox, searchButton, loader); +}); diff --git a/js/searchResults.js b/js/searchResults.js new file mode 100644 index 0000000..14a3dc2 --- /dev/null +++ b/js/searchResults.js @@ -0,0 +1,80 @@ +class SearchResults { + constructor(resultsDiv, baseUrl) { + this.resultsDiv = resultsDiv; + this.baseUrl = baseUrl; + } + + highlightText(text, query) { + const regex = new RegExp(`(${query})`, "gi"); + return text.replace(regex, "$1"); + } + + async getNameAndSymbol(name) { + let endpoint = `${this.baseUrl}/search?query=${name}&limit=10&exchange=NASDAQ`; + this.resultsDiv.innerHTML = "

.your_search_results:

"; + try { + const response = await fetch(endpoint); + const data = await response.json(); + let promises = []; + for (let i = 0; i < 10; i++) { + let symbol = data[i].symbol; + let highlightedName = this.highlightText(data[i].name, name); + let highlightedSymbol = this.highlightText(symbol, name); + promises.push(this.fetchSymbolData(symbol, i)); + this.resultsDiv.innerHTML += ` +
+ + ${highlightedName}, (${highlightedSymbol}) + ${data[i].name} icon +

+
+
+ `; + } + await Promise.all(promises); + } catch (error) { + console.log(error); + } + } + + async fetchSymbolData(symbol, index) { + try { + const response = await fetch(`${this.baseUrl}/company/profile/${symbol}`); + const data = await response.json(); + let changesNegPoz = + data.profile.changesPercentage > 0 ? "text-success" : "text-danger"; + const pEls = this.resultsDiv.querySelectorAll("div p"); + pEls[index].textContent = `Change: ${data.profile.changesPercentage}%`; + pEls[index].classList.add(changesNegPoz); + if (data.profile.changesPercentage > 0) { + pEls[index].innerHTML += ` `; + } else if (data.profile.changesPercentage < 0) { + pEls[index].innerHTML += ` `; + } + const imgEl = this.resultsDiv.querySelector( + `img[data-symbol="${symbol}"]` + ); + if (imgEl) { + imgEl.src = data.profile.image; + } + } catch (error) { + const imgEl = this.resultsDiv.querySelector( + `img[data-symbol="${symbol}"]` + ); + imgEl.style.display = "none"; + } + } +} + +const searchResults = new SearchResults( + document.getElementById("resultsDiv"), + "https://stock-exchange-dot-full-stack-course-services.ew.r.appspot.com/api/v3" +); + +document.addEventListener("DOMContentLoaded", function (event) { + document.querySelectorAll("img").forEach(function (img) { + img.onerror = function () { + this.style.display = "none"; + }; + }); +});