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 += `
+
+ `;
+ }
+ 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 += `
+
+ `;
+ }
+ 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";
+ };
+ });
+});