add related stocks to sidebar

This commit is contained in:
MuslemRahimi 2024-11-09 22:14:38 +01:00
parent 5665195ea1
commit 13447b697c
9 changed files with 272 additions and 180 deletions

View File

@ -367,11 +367,18 @@ export function abbreviateNumber(number, addDollarSign = false) {
}
if (Math.abs(number) !== 0 && Math.abs(number) > 1000) {
const suffixes = ["", "K", "M", "B", "T", "Q", "Qu", "S", "O", "N", "D"];
const suffixes = ["", "K", "M", "B", "B", "T", "Q", "Qu", "S", "O", "N", "D"];
const magnitude = Math.floor(Math.log10(Math.abs(number)));
let index = Math.min(Math.floor(magnitude / 3), suffixes.length - 1);
// Special case to keep numbers in trillions formatted as billions
if (index >= 4) {
index = 3; // Keep the suffix at "B"
}
let abbreviation = Math.abs(number) / Math.pow(10, index * 3);
// Set the desired number of decimals
if (abbreviation >= 1000) {
abbreviation = abbreviation.toFixed(1);
index++;
@ -379,37 +386,28 @@ export function abbreviateNumber(number, addDollarSign = false) {
abbreviation = abbreviation.toFixed(2);
}
abbreviation = abbreviation.toLocaleString("en-US", {
abbreviation = parseFloat(abbreviation).toLocaleString("en-US", {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
if (Math.abs(number) % 1000 === 0) {
abbreviation = abbreviation.replace("-", "");
}
if (abbreviation?.slice(-3) === ".00") {
abbreviation = abbreviation.slice(0, -3);
}
const formattedNumber = abbreviation + suffixes[index];
if (addDollarSign) {
return (negative ? "-$" : "$") + formattedNumber;
} else {
return negative ? "-" + formattedNumber : formattedNumber;
}
return addDollarSign
? (negative ? "-$" : "$") + formattedNumber
: negative
? "-" + formattedNumber
: formattedNumber;
} else if (Math.abs(number) >= 0 && Math.abs(number) < 1000) {
if (addDollarSign) {
return (negative ? "-$" : "$") + number;
} else {
return number;
}
return addDollarSign
? (negative ? "-$" : "$") + number
: number.toString();
} else {
return addDollarSign ? "$0" : "0";
}
}
export const formatDate = (dateString) => {
const date = new Date(dateString);
return formatDistanceToNow(date, {

View File

@ -0,0 +1,28 @@
export const load = async ({ locals, params }) => {
const getSimilarStocks = async () => {
const { apiKey, apiURL } = locals;
const postData = {
ticker: params.tickerID,
};
// make the POST request to the endpoint
const response = await fetch(apiURL + "/similar-stocks", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey,
},
body: JSON.stringify(postData),
});
const output = await response.json();
return output;
};
// Make sure to return a promise
return {
getSimilarStocks: await getSimilarStocks(),
};
};

View File

@ -1,37 +1,11 @@
<script lang="ts">
import { stockTicker } from "$lib/store";
import ArrowLogo from "lucide-svelte/icons/move-up-right";
export let data;
let newsList = data?.getNews ?? [];
const formatDate = (dateString) => {
// Create a date object for the input dateString
const inputDate = new Date(dateString);
// Create a date object for the current time in New York City
const nycTime = new Date().toLocaleString("en-US", {
timeZone: "America/New_York",
});
const currentNYCDate = new Date(nycTime);
// Calculate the difference in milliseconds
const difference = inputDate.getTime() - currentNYCDate.getTime();
// Convert the difference to minutes
const minutes = Math.abs(Math.round(difference / (1000 * 60)));
if (minutes < 60) {
return `${minutes} minutes`;
} else if (minutes < 1440) {
const hours = Math.round(minutes / 60);
return `${hours} hour${hours !== 1 ? "s" : ""}`;
} else {
const days = Math.round(minutes / 1440);
return `${days} day${days !== 1 ? "s" : ""}`;
}
};
const similarStocks = data?.getSimilarStocks?.sort(
(a, b) => b?.dividendYield - a?.dividendYield,
);
</script>
<section class="w-auto overflow-hidden min-h-screen">
@ -66,29 +40,43 @@
</div>
{/if}
{#if newsList?.length !== 0}
{#if similarStocks?.length > 0}
<div
class="w-full border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
class="w-full p-2 text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<div class="p-4 text-sm">
<h3 class="text-lg text-white font-semibold mb-3">
{$stockTicker} News
</h3>
<ul class="text-gray-200">
{#each newsList?.slice(0, 10) as item}
<li class="mb-3 last:mb-1">
{formatDate(item?.publishedDate)} ago -
<a
class="sm:hover:text-white text-blue-400"
href={item?.url}
target="_blank"
rel="noopener noreferrer nofollow">{item?.title}</a
<h3 class="p-2 pt-4 text-xl font-semibold">Related Stocks</h3>
<table class="table table-sm table-compact w-full text-white">
<thead class="text-white"
><tr
><th
class="whitespace-nowrap border-b font-semibold text-sm text-left"
>Company</th
>
<th
class="whitespace-nowrap border-b font-semibold text-sm text-right"
>Employees</th
></tr
></thead
>
<tbody>
{#each similarStocks?.slice(0, 8) as item}
<tr class="border-gray-600 border-b"
><td class="text-left"
><a
href={`/stocks/${item?.symbol}`}
class="sm:hover:text-white text-blue-400"
>{item?.symbol}</a
></td
>
- {item?.site}
</li>
<td class="text-right cursor-normal"
>{item?.dividendYield !== null
? item?.dividendYield + "%"
: "n/a"}</td
>
</tr>
{/each}
</ul>
</div>
</tbody>
</table>
</div>
{/if}
</aside>

View File

@ -0,0 +1,28 @@
export const load = async ({ locals, params }) => {
const getSimilarStocks = async () => {
const { apiKey, apiURL } = locals;
const postData = {
ticker: params.tickerID,
};
// make the POST request to the endpoint
const response = await fetch(apiURL + "/similar-stocks", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey,
},
body: JSON.stringify(postData),
});
const output = await response.json();
return output;
};
// Make sure to return a promise
return {
getSimilarStocks: await getSimilarStocks(),
};
};

View File

@ -1,7 +1,10 @@
<script lang="ts">
import ArrowLogo from "lucide-svelte/icons/move-up-right";
export let data;
const similarStocks = data?.getSimilarStocks?.sort(
(a, b) => b?.employees - a?.employees,
);
</script>
<section class="w-full overflow-hidden">
@ -35,44 +38,46 @@
</a>
</div>
{/if}
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<a
href={"/options-flow"}
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
{#if similarStocks?.length > 0}
<div
class="w-full p-2 text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold text-white ml-3">
Options Flow
</h2>
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" />
</div>
<span class="text-white p-3 ml-3 mr-3">
Get realtime options flow and customize your screener
</span>
</a>
</div>
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<a
href={"/stock-screener"}
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold text-white ml-3">
Stock Screener
</h2>
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" />
</div>
<span class="text-white p-3 ml-3 mr-3">
Build your Stock Screener to find profitable stocks.
</span>
</a>
</div>
<h3 class="p-2 pt-4 text-xl font-semibold">Related Stocks</h3>
<table class="table table-sm table-compact w-full text-white">
<thead class="text-white"
><tr
><th
class="whitespace-nowrap border-b font-semibold text-sm text-left"
>Company</th
>
<th
class="whitespace-nowrap border-b font-semibold text-sm text-right"
>Employees</th
></tr
></thead
>
<tbody>
{#each similarStocks?.slice(0, 8) as item}
<tr class="border-gray-600 border-b"
><td class="text-left"
><a
href={`/stocks/${item?.symbol}`}
class="sm:hover:text-white text-blue-400"
>{item?.symbol}</a
></td
>
<td class="text-right cursor-normal"
>{parseFloat(item?.employees).toLocaleString("en-US", {
maximumFractionDigits: 2,
minimumFractionDigits: 0,
})}</td
>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</aside>
</div>
</div>

View File

@ -0,0 +1,28 @@
export const load = async ({ locals, params }) => {
const getSimilarStocks = async () => {
const { apiKey, apiURL } = locals;
const postData = {
ticker: params.tickerID,
};
// make the POST request to the endpoint
const response = await fetch(apiURL + "/similar-stocks", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey,
},
body: JSON.stringify(postData),
});
const output = await response.json();
return output;
};
// Make sure to return a promise
return {
getSimilarStocks: await getSimilarStocks(),
};
};

View File

@ -1,7 +1,9 @@
<script lang="ts">
import { abbreviateNumber } from "$lib/utils";
import ArrowLogo from "lucide-svelte/icons/move-up-right";
export let data;
const similarStocks = data?.getSimilarStocks;
</script>
<section class="w-full overflow-hidden">
@ -17,7 +19,7 @@
<aside class="hidden lg:block relative fixed w-1/4 ml-4">
{#if data?.user?.tier !== "Pro" || data?.user?.freeTrial}
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
class="w-full text-white border sm:hover:border-gray-500 border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<a
href="/pricing"
@ -36,43 +38,43 @@
</div>
{/if}
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<a
href={"/price-alert"}
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
{#if similarStocks?.length > 0}
<div
class="w-full p-2 text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold text-white ml-3">
Price Alert
</h2>
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" />
</div>
<span class="text-white p-3 ml-3 mr-3">
Customize your alerts to never miss out again
</span>
</a>
</div>
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<a
href={"/stock-screener"}
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold text-white ml-3">
Stock Screener
</h2>
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" />
</div>
<span class="text-white p-3 ml-3 mr-3">
Build your Stock Screener to find profitable stocks.
</span>
</a>
</div>
<h3 class="p-2 pt-4 text-xl font-semibold">Related Stocks</h3>
<table class="table table-sm table-compact w-full text-white">
<thead class="text-white"
><tr
><th
class="whitespace-nowrap border-b font-semibold text-sm text-left"
>Company</th
>
<th
class="whitespace-nowrap border-b font-semibold text-sm text-right"
>Market Cap</th
></tr
></thead
>
<tbody>
{#each similarStocks?.slice(0, 8) as item}
<tr class="border-gray-600 border-b"
><td class="text-left"
><a
href={`/stocks/${item?.symbol}`}
class="sm:hover:text-white text-blue-400"
>{item?.symbol}</a
></td
>
<td class="text-right cursor-normal"
>{abbreviateNumber(item?.marketCap)}</td
>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</aside>
</div>
</div>

View File

@ -20,6 +20,8 @@ export const load = async ({ locals, params }) => {
return output;
};
// Make sure to return a promise
return {
getHistoricalMarketCap: await getHistoricalMarketCap(),

View File

@ -407,7 +407,7 @@
{#if rawData?.length !== 0}
<div class="grid grid-cols-1 gap-2">
<div
class="text-white p-3 sm:p-5 rounded-lg sm:flex sm:flex-row sm:items-center border border-slate-800 text-sm sm:text-[1rem]"
class="text-white p-3 sm:p-5 rounded-lg sm:flex sm:flex-row sm:items-center border border-gray-600 text-sm sm:text-[1rem]"
>
<svg
class="w-6 h-6 flex-shrink-0 inline-block sm:mr-2"
@ -436,39 +436,52 @@
</div>
<div
class="mt-5 grid grid-cols-2 gap-3 px-1 text-base xs:mt-6 bp:mt-7 bp:text-lg sm:grid-cols-3 sm:gap-6 sm:px-4 sm:text-xl"
class="mb-4 mt-5 flex flex-col divide-y divide-gray-600 rounded-md border border-gray-600 sm:grid sm:grid-cols-3 sm:divide-x sm:divide-y-0"
>
<div>
Market Cap
<div
class="mt-0.5 text-lg font-semibold bp:text-xl sm:mt-1.5 sm:text-2xl"
>
{abbreviateNumber(data?.getStockQuote?.marketCap)}
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">
Market Cap
</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
{abbreviateNumber(data?.getStockQuote?.marketCap)}
</div>
</div>
</div>
<div>
Category
<div
class="mt-0.5 text-lg font-semibold bp:text-xl sm:mt-1.5 sm:text-2xl"
>
{#if capCategory}
<a
class="sm:hover:text-white text-blue-400"
href={capCategory.link}
>
{capCategory.name}
</a>
{:else}
n/a
{/if}
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Category</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
{#if capCategory}
<a
class="sm:hover:text-white text-blue-400"
href={capCategory.link}
>
{capCategory.name}
</a>
{:else}
n/a
{/if}
</div>
</div>
</div>
<div>
1-Year Change
<div
class="mt-0.5 text-lg font-semibold bp:text-xl sm:mt-1.5 sm:text-2xl"
>
198.62%
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">
1-Year Change
</div>
<div
class="mt-1 break-words font-semibold leading-8 tiny:text-lg xs:text-xl sm:text-2xl {changePercentageYearAgo >
0
? "before:content-['+'] text-[#00FC50]"
: 'text-[#FF2F1F]'}"
>
{abbreviateNumber(changePercentageYearAgo?.toFixed(2))}%
</div>
</div>
</div>
</div>
@ -592,10 +605,10 @@
<li class="w-full">
<label
on:click={() => changeTablePeriod("annual")}
class="cursor-pointer rounded-l-lg inline-block w-full py-2.5 text-white {filterRule ===
class="cursor-pointer rounded-l-md inline-block w-full py-1.5 {filterRule ===
'annual'
? 'bg-[#fff]'
: 'bg-[#313131]'} font-semibold border-r border-gray-600"
? 'bg-[#fff] text-black'
: 'bg-[#313131] text-white'} font-semibold"
aria-current="page"
>
Annual
@ -605,17 +618,17 @@
{#if data?.user?.tier === "Pro"}
<label
on:click={() => changeTablePeriod("quarterly")}
class="cursor-pointer inline-block w-full py-2.5 {filterRule ===
class="cursor-pointer inline-block w-full py-1.5 {filterRule ===
'quarterly'
? 'bg-[#fff]'
: 'bg-[#313131]'} font-semibold text-white rounded-r-lg"
? 'bg-[#fff] text-black'
: 'bg-[#313131]'} font-semibold rounded-r-md"
>
Quartely
</label>
{:else}
<a
href="/pricing"
class="flex flex-row items-center m-auto justify-center cursor-pointer inline-block w-full py-2.5 bg-[#313131] font-semibold text-white rounded-r-lg"
class="flex flex-row items-center m-auto justify-center cursor-pointer inline-block w-full py-1.5 bg-[#313131] font-semibold text-white rounded-r-md"
>
<span class="">Quarterly</span>
<svg
@ -637,7 +650,7 @@
class="table table-sm table-compact rounded-none sm:rounded-md w-full border-bg-[#09090B] m-auto mt-4"
>
<thead>
<tr class="border border-slate-800">
<tr class="border border-gray-600">
<th
class="text-white font-semibold text-start text-sm sm:text-[1rem]"
>Date</th