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) { 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))); const magnitude = Math.floor(Math.log10(Math.abs(number)));
let index = Math.min(Math.floor(magnitude / 3), suffixes.length - 1); 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); let abbreviation = Math.abs(number) / Math.pow(10, index * 3);
// Set the desired number of decimals
if (abbreviation >= 1000) { if (abbreviation >= 1000) {
abbreviation = abbreviation.toFixed(1); abbreviation = abbreviation.toFixed(1);
index++; index++;
@ -379,37 +386,28 @@ export function abbreviateNumber(number, addDollarSign = false) {
abbreviation = abbreviation.toFixed(2); abbreviation = abbreviation.toFixed(2);
} }
abbreviation = abbreviation.toLocaleString("en-US", { abbreviation = parseFloat(abbreviation).toLocaleString("en-US", {
maximumFractionDigits: 2, maximumFractionDigits: 2,
minimumFractionDigits: 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]; const formattedNumber = abbreviation + suffixes[index];
if (addDollarSign) { return addDollarSign
return (negative ? "-$" : "$") + formattedNumber; ? (negative ? "-$" : "$") + formattedNumber
} else { : negative
return negative ? "-" + formattedNumber : formattedNumber; ? "-" + formattedNumber
} : formattedNumber;
} else if (Math.abs(number) >= 0 && Math.abs(number) < 1000) { } else if (Math.abs(number) >= 0 && Math.abs(number) < 1000) {
if (addDollarSign) { return addDollarSign
return (negative ? "-$" : "$") + number; ? (negative ? "-$" : "$") + number
} else { : number.toString();
return number;
}
} else { } else {
return addDollarSign ? "$0" : "0"; return addDollarSign ? "$0" : "0";
} }
} }
export const formatDate = (dateString) => { export const formatDate = (dateString) => {
const date = new Date(dateString); const date = new Date(dateString);
return formatDistanceToNow(date, { 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"> <script lang="ts">
import { stockTicker } from "$lib/store";
import ArrowLogo from "lucide-svelte/icons/move-up-right"; import ArrowLogo from "lucide-svelte/icons/move-up-right";
export let data; export let data;
let newsList = data?.getNews ?? []; const similarStocks = data?.getSimilarStocks?.sort(
(a, b) => b?.dividendYield - a?.dividendYield,
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" : ""}`;
}
};
</script> </script>
<section class="w-auto overflow-hidden min-h-screen"> <section class="w-auto overflow-hidden min-h-screen">
@ -66,29 +40,43 @@
</div> </div>
{/if} {/if}
{#if newsList?.length !== 0} {#if similarStocks?.length > 0}
<div <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="p-2 pt-4 text-xl font-semibold">Related Stocks</h3>
<h3 class="text-lg text-white font-semibold mb-3"> <table class="table table-sm table-compact w-full text-white">
{$stockTicker} News <thead class="text-white"
</h3> ><tr
<ul class="text-gray-200"> ><th
{#each newsList?.slice(0, 10) as item} class="whitespace-nowrap border-b font-semibold text-sm text-left"
<li class="mb-3 last:mb-1"> >Company</th
{formatDate(item?.publishedDate)} ago - >
<a <th
class="sm:hover:text-white text-blue-400" class="whitespace-nowrap border-b font-semibold text-sm text-right"
href={item?.url} >Employees</th
target="_blank" ></tr
rel="noopener noreferrer nofollow">{item?.title}</a ></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} <td class="text-right cursor-normal"
</li> >{item?.dividendYield !== null
? item?.dividendYield + "%"
: "n/a"}</td
>
</tr>
{/each} {/each}
</ul> </tbody>
</div> </table>
</div> </div>
{/if} {/if}
</aside> </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"> <script lang="ts">
import ArrowLogo from "lucide-svelte/icons/move-up-right"; import ArrowLogo from "lucide-svelte/icons/move-up-right";
export let data; export let data;
const similarStocks = data?.getSimilarStocks?.sort(
(a, b) => b?.employees - a?.employees,
);
</script> </script>
<section class="w-full overflow-hidden"> <section class="w-full overflow-hidden">
@ -35,44 +38,46 @@
</a> </a>
</div> </div>
{/if} {/if}
{#if similarStocks?.length > 0}
<div <div
class="w-full text-white 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"
>
<a
href={"/options-flow"}
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"> <h3 class="p-2 pt-4 text-xl font-semibold">Related Stocks</h3>
<h2 class="text-start text-xl font-semibold text-white ml-3"> <table class="table table-sm table-compact w-full text-white">
Options Flow <thead class="text-white"
</h2> ><tr
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" /> ><th
</div> class="whitespace-nowrap border-b font-semibold text-sm text-left"
<span class="text-white p-3 ml-3 mr-3"> >Company</th
Get realtime options flow and customize your screener >
</span> <th
</a> class="whitespace-nowrap border-b font-semibold text-sm text-right"
</div> >Employees</th
></tr
<div ></thead
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer" >
> <tbody>
<a {#each similarStocks?.slice(0, 8) as item}
href={"/stock-screener"} <tr class="border-gray-600 border-b"
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0" ><td class="text-left"
> ><a
<div class="w-full flex justify-between items-center p-3 mt-3"> href={`/stocks/${item?.symbol}`}
<h2 class="text-start text-xl font-semibold text-white ml-3"> class="sm:hover:text-white text-blue-400"
Stock Screener >{item?.symbol}</a
</h2> ></td
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" /> >
</div> <td class="text-right cursor-normal"
<span class="text-white p-3 ml-3 mr-3"> >{parseFloat(item?.employees).toLocaleString("en-US", {
Build your Stock Screener to find profitable stocks. maximumFractionDigits: 2,
</span> minimumFractionDigits: 0,
</a> })}</td
</div> >
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</aside> </aside>
</div> </div>
</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"> <script lang="ts">
import { abbreviateNumber } from "$lib/utils";
import ArrowLogo from "lucide-svelte/icons/move-up-right"; import ArrowLogo from "lucide-svelte/icons/move-up-right";
export let data; export let data;
const similarStocks = data?.getSimilarStocks;
</script> </script>
<section class="w-full overflow-hidden"> <section class="w-full overflow-hidden">
@ -17,7 +19,7 @@
<aside class="hidden lg:block relative fixed w-1/4 ml-4"> <aside class="hidden lg:block relative fixed w-1/4 ml-4">
{#if data?.user?.tier !== "Pro" || data?.user?.freeTrial} {#if data?.user?.tier !== "Pro" || data?.user?.freeTrial}
<div <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 <a
href="/pricing" href="/pricing"
@ -36,43 +38,43 @@
</div> </div>
{/if} {/if}
<div {#if similarStocks?.length > 0}
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer" <div
> class="w-full p-2 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"
> >
<div class="w-full flex justify-between items-center p-3 mt-3"> <h3 class="p-2 pt-4 text-xl font-semibold">Related Stocks</h3>
<h2 class="text-start text-xl font-semibold text-white ml-3"> <table class="table table-sm table-compact w-full text-white">
Price Alert <thead class="text-white"
</h2> ><tr
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" /> ><th
</div> class="whitespace-nowrap border-b font-semibold text-sm text-left"
<span class="text-white p-3 ml-3 mr-3"> >Company</th
Customize your alerts to never miss out again >
</span> <th
</a> class="whitespace-nowrap border-b font-semibold text-sm text-right"
</div> >Market Cap</th
></tr
<div ></thead
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer" >
> <tbody>
<a {#each similarStocks?.slice(0, 8) as item}
href={"/stock-screener"} <tr class="border-gray-600 border-b"
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0" ><td class="text-left"
> ><a
<div class="w-full flex justify-between items-center p-3 mt-3"> href={`/stocks/${item?.symbol}`}
<h2 class="text-start text-xl font-semibold text-white ml-3"> class="sm:hover:text-white text-blue-400"
Stock Screener >{item?.symbol}</a
</h2> ></td
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" /> >
</div> <td class="text-right cursor-normal"
<span class="text-white p-3 ml-3 mr-3"> >{abbreviateNumber(item?.marketCap)}</td
Build your Stock Screener to find profitable stocks. >
</span> </tr>
</a> {/each}
</div> </tbody>
</table>
</div>
{/if}
</aside> </aside>
</div> </div>
</div> </div>

View File

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

View File

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