restrict watchlist & price alerts

This commit is contained in:
MuslemRahimi 2025-02-12 19:59:29 +01:00
parent b071412f29
commit 93a9ae46ab
7 changed files with 281 additions and 156 deletions

View File

@ -94,7 +94,8 @@
}
} else {
toast.error("Only for Pro Members", {
style: "border-radius: 200px; background: #2A2E39; color: #fff;",
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
}
}

View File

@ -3,11 +3,12 @@ import { serialize } from "object-to-formdata";
export const POST: RequestHandler = async ({ request, locals }) => {
const data = await request.json();
const { pb } = locals;
const { pb, user } = locals;
let output;
try {
if(user?.tier === 'Pro') {
try {
// Ensure itemIdList is always an array.
const itemIdList = Array.isArray(data?.itemIdList)
? data?.itemIdList
@ -48,4 +49,11 @@ export const POST: RequestHandler = async ({ request, locals }) => {
}
return new Response(JSON.stringify(output?.id));
} else {
return new Response(JSON.stringify("failure"));
}
};

View File

@ -5,39 +5,95 @@ export const POST = (async ({ request, locals }) => {
const { user, pb } = locals;
const data = await request.json();
const ticker = data?.ticker; // This can be a string (single ticker) or an array (list of tickers)
// `tickerInput` can be a string (single ticker) or an array (list of tickers)
const tickerInput = data?.ticker || [];
const watchListId = data?.watchListId;
let output;
// Determine the ticker limit: 50 for Pro users, 5 for non-Pro users.
const isProUser = user?.tier === "Pro";
const tickerLimit = isProUser ? 100 : 5;
try {
const watchList = await pb.collection("watchlist").getOne(watchListId);
// Ensure current tickers are in an array.
let currentTickers = watchList?.ticker;
if (typeof currentTickers === "string") {
try {
currentTickers = JSON.parse(currentTickers);
} catch (err) {
currentTickers = [];
}
}
currentTickers = currentTickers || [];
if (Array.isArray(ticker)) {
// If `ticker` is a list, update the watchlist directly with the new list of tickers.
output = await pb.collection("watchlist").update(watchListId, {
ticker: ticker,
if (Array.isArray(tickerInput)) {
// When replacing the entire ticker list:
if(data?.mode === 'delete') {
output = await pb.collection("watchlist").update(watchListId, {
ticker: tickerInput,
});
} else if (watchList?.ticker?.includes(ticker)) {
// Remove single ticker from the watchlist if it's already present.
const newTickerList = watchList?.ticker.filter((item) => item !== ticker);
output = await pb
.collection("watchlist")
.update(watchListId, { ticker: newTickerList });
} else {
if (tickerInput.length > tickerLimit) {
return new Response(
JSON.stringify({
error: isProUser
? `You can only have up to ${tickerLimit} stocks in your watchlist.`
: `Upgrade to Pro to add unlimited stocks!`,
}),
{ status: 403 }
);
}
output = await pb.collection("watchlist").update(watchListId, {
ticker: tickerInput,
});
}
} else {
// Add single ticker to the watchlist if it's not present.
const newTickerList = [...watchList?.ticker, ticker];
output = await pb
.collection("watchlist")
.update(watchListId, { ticker: newTickerList });
// Single ticker update.
if (currentTickers?.includes(tickerInput)) {
// Remove the ticker if it's already present.
const newTickerList = currentTickers?.filter((item) => item !== tickerInput);
output = await pb.collection("watchlist").update(watchListId, { ticker: newTickerList });
} else {
// Add the ticker if not already present.
const newTickerList = [...currentTickers, tickerInput];
if (newTickerList.length > tickerLimit) {
return new Response(
JSON.stringify({
error: isProUser
? `You can only have up to ${tickerLimit} stocks in your watchlist.`
: `Upgrade to Pro to add unlimited stocks`,
}),
{ status: 403 }
);
}
output = await pb.collection("watchlist").update(watchListId, { ticker: newTickerList });
}
}
} catch (e) {
// If the watchlist doesn't exist, create a new one with either the single ticker or list.
// If the watchlist doesn't exist, create a new one.
const tickersArray = Array.isArray(tickerInput) ? tickerInput : [tickerInput];
if (tickersArray.length > tickerLimit) {
return new Response(
JSON.stringify({
error: isProUser
? `You can only have up to ${tickerLimit} stocks in your watchlist.`
: `Upgrade to Pro to add unlimited stocks!`,
}),
{ status: 403 }
);
}
output = await pb.collection("watchlist").create(
serialize({
user: user?.id,
ticker: Array.isArray(ticker)
? JSON.stringify(ticker)
: JSON.stringify([ticker]),
ticker: JSON.stringify(tickersArray),
title: "Favorites",
})
);

View File

@ -83,57 +83,65 @@
async function toggleUserWatchlist(watchListId: string) {
try {
isTickerIncluded = !isTickerIncluded;
// Find the index of the watchlist
const watchlistIndex = userWatchList?.findIndex(
(item) => item?.id === watchListId,
);
if (watchlistIndex !== -1) {
const existingTickerIndex =
userWatchList[watchlistIndex]?.ticker?.indexOf($etfTicker);
if (watchlistIndex !== -1 && watchlistIndex !== undefined) {
const watchlist = userWatchList[watchlistIndex];
const existingTickerIndex = watchlist?.ticker?.indexOf($etfTicker);
let updatedTickers = [...(watchlist?.ticker || [])]; // Ensure we don't mutate directly
if (existingTickerIndex !== -1) {
// If the $etfTicker exists, remove it from the array
userWatchList[watchlistIndex]?.ticker?.splice(existingTickerIndex, 1);
// Remove the ticker if it exists
updatedTickers.splice(existingTickerIndex, 1);
} else {
// If the $etfTicker doesn't exist, add it to the array
userWatchList[watchlistIndex]?.ticker?.push($etfTicker);
// Add the ticker if it doesn't exist
updatedTickers.push($etfTicker);
// Check tier limits
if (data?.user?.tier !== "Pro" && updatedTickers.length > 5) {
toast.error("Upgrade to Pro to add unlimited stocks!", {
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
return;
}
}
// Update the userWatchList
userWatchList = [...userWatchList];
}
// Update the local state immutably
userWatchList = userWatchList.map((item, idx) =>
idx === watchlistIndex ? { ...item, ticker: updatedTickers } : item,
);
const postData = {
watchListId: watchListId,
ticker: $etfTicker,
};
// Send API request
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ watchListId, ticker: $etfTicker }),
});
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
const output = await response.json();
if (!response.ok) {
throw new Error("Network response was not ok");
}
const output = await response.json();
// Update the userWatchList with the response from the server
if (watchlistIndex !== -1) {
userWatchList[watchlistIndex] = output;
userWatchList = [...userWatchList];
// Update userWatchList based on API response
userWatchList = userWatchList.map((item) =>
item.id === watchListId ? output : item,
);
} else {
// If watchlist doesn't exist, create a new entry
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ watchListId, ticker: $etfTicker }),
});
const output = await response.json();
userWatchList = [...userWatchList, output];
}
} catch (error) {
console.error("An error occurred:", error);
// Handle the error appropriately (e.g., show an error message to the user)
}
}

View File

@ -81,57 +81,65 @@
async function toggleUserWatchlist(watchListId: string) {
try {
isTickerIncluded = !isTickerIncluded;
// Find the index of the watchlist
const watchlistIndex = userWatchList?.findIndex(
(item) => item?.id === watchListId,
);
if (watchlistIndex !== -1) {
const existingTickerIndex =
userWatchList[watchlistIndex]?.ticker?.indexOf($indexTicker);
if (watchlistIndex !== -1 && watchlistIndex !== undefined) {
const watchlist = userWatchList[watchlistIndex];
const existingTickerIndex = watchlist?.ticker?.indexOf($indexTicker);
let updatedTickers = [...(watchlist?.ticker || [])]; // Ensure we don't mutate directly
if (existingTickerIndex !== -1) {
// If the $indexTicker exists, remove it from the array
userWatchList[watchlistIndex]?.ticker?.splice(existingTickerIndex, 1);
// Remove the ticker if it exists
updatedTickers.splice(existingTickerIndex, 1);
} else {
// If the $indexTicker doesn't exist, add it to the array
userWatchList[watchlistIndex]?.ticker?.push($indexTicker);
// Add the ticker if it doesn't exist
updatedTickers.push($indexTicker);
// Check tier limits
if (data?.user?.tier !== "Pro" && updatedTickers.length > 5) {
toast.error("Upgrade to Pro to add unlimited stocks!", {
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
return;
}
}
// Update the userWatchList
userWatchList = [...userWatchList];
}
// Update the local state immutably
userWatchList = userWatchList.map((item, idx) =>
idx === watchlistIndex ? { ...item, ticker: updatedTickers } : item,
);
const postData = {
watchListId: watchListId,
ticker: $indexTicker,
};
// Send API request
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ watchListId, ticker: $indexTicker }),
});
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
const output = await response.json();
if (!response.ok) {
throw new Error("Network response was not ok");
}
const output = await response.json();
// Update the userWatchList with the response from the server
if (watchlistIndex !== -1) {
userWatchList[watchlistIndex] = output;
userWatchList = [...userWatchList];
// Update userWatchList based on API response
userWatchList = userWatchList.map((item) =>
item.id === watchListId ? output : item,
);
} else {
// If watchlist doesn't exist, create a new entry
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ watchListId, ticker: $indexTicker }),
});
const output = await response.json();
userWatchList = [...userWatchList, output];
}
} catch (error) {
console.error("An error occurred:", error);
// Handle the error appropriately (e.g., show an error message to the user)
}
}
@ -317,7 +325,8 @@
}
}
$: isTickerIncluded = userWatchList?.some(
(item) => item.user === data?.user?.id && item.ticker?.includes($indexTicker),
(item) =>
item.user === data?.user?.id && item.ticker?.includes($indexTicker),
);
$: charNumber = $screenWidth < 640 ? 25 : 40;
@ -372,7 +381,10 @@
class="flex-1 flex-shrink-0 flex flex-row items-center justify-between -mt-2"
>
<a
href={/^\/(stocks|etf|index)/.test($previousPage || "") ? "/" : $previousPage || "/"}>
href={/^\/(stocks|etf|index)/.test($previousPage || "")
? "/"
: $previousPage || "/"}
>
<svg
class="w-5 h-5 inline-block"
xmlns="http://www.w3.org/2000/svg"
@ -425,7 +437,9 @@
<label
class="mr-4"
on:click={() =>
shareContent("https://stocknear.com/index/" + $indexTicker)}
shareContent(
"https://stocknear.com/index/" + $indexTicker,
)}
>
<svg
class="w-6 h-6 inline-block"
@ -880,8 +894,6 @@
Options
</a>
<a
href={`/index/${$indexTicker}/history`}
on:click={() => changeSection("history")}

View File

@ -92,57 +92,65 @@
async function toggleUserWatchlist(watchListId: string) {
try {
isTickerIncluded = !isTickerIncluded;
// Find the index of the watchlist
const watchlistIndex = userWatchList?.findIndex(
(item) => item?.id === watchListId,
);
if (watchlistIndex !== -1) {
const existingTickerIndex =
userWatchList[watchlistIndex]?.ticker?.indexOf($stockTicker);
if (watchlistIndex !== -1 && watchlistIndex !== undefined) {
const watchlist = userWatchList[watchlistIndex];
const existingTickerIndex = watchlist?.ticker?.indexOf($stockTicker);
let updatedTickers = [...(watchlist?.ticker || [])]; // Ensure we don't mutate directly
if (existingTickerIndex !== -1) {
// If the $stockTicker exists, remove it from the array
userWatchList[watchlistIndex]?.ticker?.splice(existingTickerIndex, 1);
// Remove the ticker if it exists
updatedTickers.splice(existingTickerIndex, 1);
} else {
// If the $stockTicker doesn't exist, add it to the array
userWatchList[watchlistIndex]?.ticker?.push($stockTicker);
// Add the ticker if it doesn't exist
updatedTickers.push($stockTicker);
// Check tier limits
if (data?.user?.tier !== "Pro" && updatedTickers.length > 5) {
toast.error("Upgrade to Pro to add unlimited stocks!", {
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
return;
}
}
// Update the userWatchList
userWatchList = [...userWatchList];
}
// Update the local state immutably
userWatchList = userWatchList.map((item, idx) =>
idx === watchlistIndex ? { ...item, ticker: updatedTickers } : item,
);
const postData = {
watchListId: watchListId,
ticker: $stockTicker,
};
// Send API request
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ watchListId, ticker: $stockTicker }),
});
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
const output = await response.json();
if (!response.ok) {
throw new Error("Network response was not ok");
}
const output = await response.json();
// Update the userWatchList with the response from the server
if (watchlistIndex !== -1) {
userWatchList[watchlistIndex] = output;
userWatchList = [...userWatchList];
// Update userWatchList based on API response
userWatchList = userWatchList.map((item) =>
item.id === watchListId ? output : item,
);
} else {
// If watchlist doesn't exist, create a new entry
const response = await fetch("/api/update-watchlist", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ watchListId, ticker: $stockTicker }),
});
const output = await response.json();
userWatchList = [...userWatchList, output];
}
} catch (error) {
console.error("An error occurred:", error);
// Handle the error appropriately (e.g., show an error message to the user)
}
}
@ -389,7 +397,9 @@
class="flex-1 flex-shrink-0 flex flex-row items-center justify-between -mt-2"
>
<a
href={/^\/(stocks|etf|index)/.test($previousPage || "") ? "/" : $previousPage || "/"}
href={/^\/(stocks|etf|index)/.test($previousPage || "")
? "/"
: $previousPage || "/"}
>
<svg
class="w-5 h-5 inline-block"

View File

@ -472,6 +472,7 @@
const postData = {
ticker: watchList?.map((item) => item?.symbol),
watchListId: displayWatchList?.id,
mode: "delete",
};
const response = await fetch("/api/update-watchlist", {
@ -513,57 +514,86 @@
}
displayList = rawTabData?.slice(0, 8);
}
async function handleAddTicker(event, ticker) {
// Ensure inputValue is reset
if (!watchList?.some((item) => item?.symbol === ticker)) {
} else {
toast.error(`This symbol is already in your watchlist`, {
async function handleAddTicker(event, ticker) {
event.preventDefault(); // Prevent the default form submission behavior
// Check if the ticker is already in the watchlist.
if (watchList?.some((item) => item?.symbol === ticker)) {
toast.error("This symbol is already in your watchlist", {
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
});
inputValue = "";
event.preventDefault();
return;
}
// Exit edit mode
// Exit edit mode.
editMode = false;
// Prepare the data to send to the API
// Prepare the data to send to the API.
const postData = {
ticker: ticker,
watchListId: displayWatchList?.id,
};
// Send the updated watchlist to the server
const response = await fetch("/api/update-watchlist", {
// Create a promise for the fetch request.
const promise = fetch("/api/update-watchlist", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
// Update the allList with the new watchlist
allList = allList?.map((item) => {
if (item?.id === displayWatchList?.id) {
return { ...item, tickers: watchList }; // Update tickers in the watchlist
}).then(async (response) => {
const output = await response.json();
// If the response is not OK, throw an error with the message from the API.
if (!response.ok) {
throw new Error(output.error || "Failed to update watchlist");
}
return item; // Return unchanged item
return output;
});
// Refresh the displayWatchList with the updated watchlist
displayWatchList = allList?.find(
(item) => item?.id === displayWatchList?.id,
// Use toast.promise to display notifications based on the promise's state.
toast.promise(
promise,
{
loading: "Updating watchlist...",
success: "Watchlist updated successfully!",
error: (err) => err.message || "Failed to update watchlist",
},
{
style:
"border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;",
},
);
// Fetch the updated watchlist data (assuming this function refreshes the UI or state)
await getWatchlistData();
try {
// Await the promise, which returns the updated watchlist data.
const updatedData = await promise;
inputValue = "";
event?.preventDefault();
// Update the local allList with the updated watchlist.
// (Assuming updatedData.ticker contains the new ticker list.)
allList = allList?.map((item) => {
if (item?.id === displayWatchList?.id) {
return { ...item, tickers: updatedData.ticker };
}
return item;
});
// Refresh displayWatchList from the updated list.
displayWatchList = allList?.find(
(item) => item?.id === displayWatchList?.id,
);
// Refresh the watchlist data (UI or state refresh).
await getWatchlistData();
// Reset the input value.
inputValue = "";
} catch (error) {
console.error("Error updating watchlist:", error);
// Note: The error toast is already displayed by toast.promise.
}
}
async function handleResetAll() {