diff --git a/src/lib/components/PriceAlert.svelte b/src/lib/components/PriceAlert.svelte index 80628139..cb4af31f 100644 --- a/src/lib/components/PriceAlert.svelte +++ b/src/lib/components/PriceAlert.svelte @@ -15,44 +15,70 @@ } async function handleCreateAlert() { + // Validate input locally. if (targetPrice < 0) { - toast.error(`Target Price must be above zero`, { + toast.error("Target Price must be above zero", { style: "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", }); - } else { - const closePopup = document.getElementById("priceAlertModal"); - closePopup?.dispatchEvent(new MouseEvent("click")); + return; + } - const postData = { - userId: data?.user?.id, - symbol: ticker, - name: data?.getStockQuote?.name, - assetType: assetType, - priceWhenCreated: currentPrice, - condition: condition, - targetPrice: targetPrice, - }; + // Optionally close the modal popup. + const closePopup = document.getElementById("priceAlertModal"); + closePopup?.dispatchEvent(new MouseEvent("click")); - // Make the POST request to the endpoint + // Prepare data for the POST request. + const postData = { + userId: data?.user?.id, + symbol: ticker, + name: data?.getStockQuote?.name, + assetType: assetType, + priceWhenCreated: currentPrice, + condition: condition, + targetPrice: targetPrice, + }; - toast.success(`Successfully created price alert`, { + // Create a promise for the POST request. + const promise = fetch("/api/create-price-alert", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(postData), + }).then(async (response) => { + const result = await response.json(); + // If the response is not ok, throw an error to be caught by toast.promise. + if (!response.ok) { + throw new Error(result.error || "Failed to create price alert"); + } + return result; + }); + + // Use toast.promise to handle pending, success, and error states. + toast.promise( + promise, + { + loading: "Creating price alert...", + success: "Successfully created price alert", + error: (err) => err.message || "Failed to create price alert", + }, + { style: "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", - }); + }, + ); - const response = await fetch("/api/create-price-alert", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(postData), - }); - - $newPriceAlertData = await response?.json(); - - //const output = await response.json(); + // Await the promise and handle the result. + try { + const newPriceAlertData = await promise; + // Update reactive store or state as needed. + $newPriceAlertData = newPriceAlertData; + // Optionally reset targetPrice or perform further actions. targetPrice = currentPrice; + } catch (error) { + // The error is already handled by toast.promise, but you can log it here. + console.error("Error creating price alert:", error); } } diff --git a/src/routes/api/create-price-alert/+server.ts b/src/routes/api/create-price-alert/+server.ts index 685cb44e..5fdc8eef 100644 --- a/src/routes/api/create-price-alert/+server.ts +++ b/src/routes/api/create-price-alert/+server.ts @@ -1,30 +1,60 @@ import type { RequestHandler } from "./$types"; export const POST: RequestHandler = async ({ request, locals }) => { - const { pb } = locals; + const { user, pb } = locals; const data = await request.json(); - let output; + if (user?.id) { - let newAlert = { - 'user': data['userId'], - 'symbol': data['symbol']?.toUpperCase(), - 'name': data['name'], - 'assetType': data['assetType']?.toLowerCase(), - 'targetPrice': Number(data['targetPrice']), - 'condition': data['condition']?.toLowerCase(), - 'priceWhenCreated': Number(data['priceWhenCreated']), - 'triggered': false, + + // If the user is not Pro, check the current number of active price alerts. + if (user?.tier !== 'Pro') { + const totalAlerts = await pb.collection("priceAlert").getFullList({ + // Ensure the filter checks for a boolean false. + filter: `user="${user?.id}" && triggered=false` + }); + + // If the user already has 3 or more active alerts, return an error. + if (totalAlerts?.length >= 3) { + return new Response( + JSON.stringify({ error: "Price alert limit reached for non-Pro users" }), + { status: 403 } + ); } + } - - try { + // Prepare the new alert data. + const newAlert = { + user: user?.id, + symbol: data['symbol']?.toUpperCase(), + name: data['name'], + assetType: data['assetType']?.toLowerCase(), + targetPrice: Number(data['targetPrice']), + condition: data['condition']?.toLowerCase(), + priceWhenCreated: Number(data['priceWhenCreated']), + triggered: false, + }; - output = await pb.collection("priceAlert")?.create(newAlert) - - } catch (err) { - output = {} - } + let output; + try { + output = await pb.collection("priceAlert").create(newAlert); + } catch (err) { + // Return an error response if the alert could not be created. + return new Response( + JSON.stringify({ error: "Error creating price alert" }), + { status: 500 } + ); + } return new Response(JSON.stringify(output)); + + } + else { + + return new Response( + JSON.stringify({ error: "Error creating price alert" }), + { status: 500 } + ); + + } }; diff --git a/src/routes/api/create-watchlist/+server.ts b/src/routes/api/create-watchlist/+server.ts index 029b93b6..d4bf949c 100644 --- a/src/routes/api/create-watchlist/+server.ts +++ b/src/routes/api/create-watchlist/+server.ts @@ -1,17 +1,33 @@ import type { RequestHandler } from "./$types"; export const POST: RequestHandler = async ({ request, locals }) => { - const { pb } = locals; + const { user, pb } = locals; const data = await request.json(); let output; - try { - output = await pb.collection("watchlist").create(data) + // For non-Pro users, ensure they can only have 1 watchlist. + if (user?.tier !== 'Pro') { + // Get the current watchlists for the user. + const existingWatchlists = await pb.collection("watchlist").getFullList({ + filter: `user="${user.id}"` + }); + + // If the user already has a watchlist, return an error response. + if (existingWatchlists.length >= 1) { + return new Response( + JSON.stringify({ error: "Non-Pro users can only have 1 watchlist" }), + { status: 403 } + ); } - catch(e) { - //console.log(e) - output = {}; - } - console.log(output) + } + + // If the user is Pro or doesn't have a watchlist yet, attempt to create one. + try { + output = await pb.collection("watchlist").create(data); + } catch (err) { + // Optionally, log the error or adjust the error message as needed. + output = { error: "Failed to create watchlist" }; + } + return new Response(JSON.stringify(output)); }; diff --git a/src/routes/watchlist/stocks/+page.svelte b/src/routes/watchlist/stocks/+page.svelte index 7975b622..357ce577 100644 --- a/src/routes/watchlist/stocks/+page.svelte +++ b/src/routes/watchlist/stocks/+page.svelte @@ -287,13 +287,13 @@ } async function createWatchList(event) { - event.preventDefault(); // prevent the default form submission behavior - - const formData = new FormData(event.target); // create a FormData object from the form + event.preventDefault(); // Prevent the default form submission behavior + const formData = new FormData(event.target); // Create a FormData object from the form const title = formData.get("title"); - if (!title || title?.length === 0) { + // Validate the title input + if (!title || title.toString().trim().length === 0) { toast.error("Title cannot be empty!", { style: "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", @@ -301,7 +301,7 @@ return; } - if (title?.length > 100) { + if (title.toString().length > 100) { toast.error("Title is too long. Keep it simple and concise bruv!", { style: "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", @@ -309,54 +309,66 @@ return; } - const postData = {}; - - // Iterate through the FormData entries and populate the object - for (const [key, value] of formData?.entries()) { + // Build the POST data object + const postData: Record = {}; + for (const [key, value] of formData.entries()) { postData[key] = value; } - try { - const response = await fetch("/api/create-watchlist", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(postData), - }); // make a POST request to the server with the FormData object - if (response?.ok) { - const output = await response.json(); - try { - // Save the version along with the rules - localStorage?.setItem( - "last-watchlist-id", - JSON?.stringify(output?.id), - ); - } catch (e) { - console.log("Failed saving indicator rules: ", e); - } - toast.success("Watchlist created successfully!", { - style: - "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", - }); - - const clicked = document.getElementById("addWatchlist"); - clicked?.dispatchEvent(new MouseEvent("click")); - const anchor = document.createElement("a"); - anchor.href = "/watchlist/stocks"; - anchor.dispatchEvent(new MouseEvent("click")); - } else { - toast.error("Something went wrong. Please try again!", { - style: - "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", - }); + // Create a promise for the POST request + const promise = fetch("/api/create-watchlist", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(postData), + }).then(async (response) => { + const output = await response.json(); + if (!response.ok) { + // If the server returned an error (e.g. non‑Pro user already has a watchlist), + // throw an error to be caught by toast.promise. + throw new Error( + output.error || "Something went wrong. Please try again!", + ); } - } catch (error) { - console.error("Error:", error); - toast.error("An error occurred. Please try again later.", { + return output; + }); + + // Use toast.promise to display a loading toast, then a success or error message + toast.promise( + promise, + { + loading: "Creating watchlist...", + success: "Watchlist created successfully!", + error: (err) => + err.message || "Something went wrong. Please try again!", + }, + { style: "border-radius: 5px; background: #fff; color: #000; border-color: #4B5563; font-size: 15px;", - }); + }, + ); + + try { + const output = await promise; + + // Save the watchlist ID to localStorage (optional) + try { + localStorage.setItem("last-watchlist-id", JSON.stringify(output?.id)); + } catch (e) { + console.log("Failed saving watchlist id: ", e); + } + + // Dispatch events to close the modal and navigate to the watchlist page + const clicked = document.getElementById("addWatchlist"); + clicked?.dispatchEvent(new MouseEvent("click")); + + const anchor = document.createElement("a"); + anchor.href = "/watchlist/stocks"; + anchor.dispatchEvent(new MouseEvent("click")); + } catch (error) { + console.error("Error creating watchlist:", error); + // No additional toast.error is needed here since toast.promise already handles errors. } } @@ -633,6 +645,7 @@ try { const savedRules = localStorage?.getItem("watchlist-ruleOfList"); const savedLastWatchlistId = localStorage?.getItem("last-watchlist-id"); + if (savedRules) { const parsedRules = JSON.parse(savedRules); @@ -666,21 +679,30 @@ } // Update checked items and sort the indicators - checkedItems = new Set(ruleOfList.map((item) => item.name)); + checkedItems = new Set(ruleOfList?.map((item) => item.name)); allRows = sortIndicatorCheckMarks(allRows); - if ( - typeof savedLastWatchlistId !== "undefined" && - savedLastWatchlistId?.length > 0 - ) { + // Parse savedLastWatchlistId safely + if (savedLastWatchlistId && savedLastWatchlistId.length > 0) { + let parsedLastWatchlistId; + try { + parsedLastWatchlistId = JSON.parse(savedLastWatchlistId); + } catch (error) { + console.error( + "Error parsing last watchlist id from localStorage. Using raw value instead.", + error, + ); + parsedLastWatchlistId = savedLastWatchlistId; + } + displayWatchList = allList?.find( - (item) => item?.id === JSON?.parse(savedLastWatchlistId), + (item) => item?.id === parsedLastWatchlistId, ); } - // If no valid watchlist found, default to the first element of allList + // If no valid watchlist is found, default to the first element of allList if (!displayWatchList && allList?.length > 0) { - displayWatchList = allList?.at(0); + displayWatchList = allList.at(0); } else if (!displayWatchList) { displayWatchList = {}; } @@ -696,7 +718,7 @@ isLoaded = true; } catch (e) { - console.log(e); + console.error("onMount error:", e); } if ($isOpen) {