add notitication channels

This commit is contained in:
MuslemRahimi 2025-02-04 14:00:24 +01:00
parent cb1e41793c
commit 5da3db056d
8 changed files with 224 additions and 68 deletions

View File

@ -80,7 +80,7 @@
<dialog id="installModal" class="modal overflow-hidden p-3 sm:p-0">
<label for="installModal" class="cursor-pointer modal-backdrop"></label>
<div class="modal-box rounded w-full bg-primary border border-gray-600">
<div class="modal-box rounded w-full bg-secondary border border-gray-600">
<div class="flex flex-row items-center pt-5">
<h4 class="text-white text-2xl font-bold text-center m-auto">
Steps to install

View File

@ -0,0 +1,22 @@
import type { RequestHandler } from "./$types";
export const POST: RequestHandler = async ({ request, locals }) => {
const data = await request.json();
const { pb } = locals;
let output = 'failure';
try {
await pb.collection("notificationChannels").update(data?.id, {
'earningsSurprise': data?.earningsSurprise,
'wiim': data?.wiim,
})
output = 'success';
}
catch(e) {
console.log(e)
output = 'failure';
}
return new Response(JSON.stringify(output));
};

View File

@ -1,10 +1,11 @@
<script lang="ts">
import { formatDate } from "$lib/utils";
import ArrowLogo from "lucide-svelte/icons/move-up-right";
import avatar from "$lib/images/visual_mod.webp";
import HoverStockChart from "$lib/components/HoverStockChart.svelte";
import { onMount } from "svelte";
import { numberOfUnreadNotification } from "$lib/store";
import SEO from "$lib/components/SEO.svelte";
import Infobox from "$lib/components/Infobox.svelte";
export let data;
export let form;
@ -57,35 +58,10 @@
});
</script>
<svelte:head>
<title>
{$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ""} Notifications
· Stocknear</title
>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="description" content="Free Stock Analysis" />
<!-- Other meta tags -->
<meta property="og:title" content="Notifications · Stocknear" />
<meta property="og:description" content="Free Stock Analysis" />
<meta
property="og:image"
content="https://stocknear-pocketbase.s3.amazonaws.com/logo/meta_logo.jpg"
/>
<meta property="og:type" content="website" />
<!-- Add more Open Graph meta tags as needed -->
<!-- Twitter specific meta tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Notifications · Stocknear" />
<meta name="twitter:description" content="Free Stock Analysis" />
<meta
name="twitter:image"
content="https://stocknear-pocketbase.s3.amazonaws.com/logo/meta_logo.jpg"
/>
<!-- Add more Twitter meta tags as needed -->
</svelte:head>
<SEO
title="Stock Market Notifications | Real-Time Updates & Alerts"
description="Stay informed with real-time stock market notifications. Get instant alerts on price changes, trends, and market movements to make smarter investment decisions."
/>
<section
class="w-full max-w-3xl sm:max-w-[1400px] overflow-hidden min-h-screen pb-20 pt-5 px-4 lg:px-3"
@ -102,20 +78,26 @@
<div
class="relative flex justify-center items-start overflow-hidden w-full"
>
<main class="w-full lg:w-3/4 lg:pr-5">
<div class="mb-6 border-b-[2px]">
<main class="w-full lg:w-3/4 lg:pr-10">
<div class="mb-3 border-b-[2px]">
<h1 class="mb-1 text-white text-2xl sm:text-3xl font-bold">
Notification
</h1>
</div>
<div class="mb-4">
<Infobox
text="Personalize your notifications on your account page."
/>
</div>
{#if notificationList?.length !== 0}
<div class="flex flex-col items-start w-full text-white">
{#each notificationList as item}
{#if item?.notifyType === "priceAlert"}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="sm:hover:bg-[#2B2B2B] pb-3 sm:p-3 mb-6 sm:mb-3 text-gray-200 w-full {!item?.readed
class=" pb-3 sm:p-3 mb-6 sm:mb-3 text-white w-full {!item?.readed
? 'bg-[#F9AB00] bg-opacity-[0.1]'
: ''} "
>
@ -163,7 +145,7 @@
{:else if item?.notifyType === "wiim"}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="sm:hover:bg-[#2B2B2B] pb-3 sm:p-3 mb-6 sm:mb-3 text-gray-200 w-full {!item?.readed
class=" pb-3 sm:p-3 mb-6 sm:mb-3 text-white w-full {!item?.readed
? 'bg-[#F9AB00] bg-opacity-[0.1]'
: ''} "
>
@ -207,7 +189,7 @@
</div>
{:else if item?.notifyType === "earningsSurprise"}
<div
class="sm:hover:bg-[#2B2B2B] pb-3 sm:p-3 mb-6 sm:mb-3 text-gray-200 w-full {!item?.readed
class=" pb-3 sm:p-3 mb-6 sm:mb-3 text-white w-full {!item?.readed
? 'bg-[#F9AB00] bg-opacity-[0.1]'
: ''} "
>

View File

@ -47,18 +47,7 @@ export const GET = async ({ locals, url, cookies }) => {
.authWithOAuth2Code(provider.name, code, expectedVerifier, redirectURL);
//oauthUsername = generateUsername(newUser['meta']['name'].split(' ').join('')).toLowerCase();
// Check if user was created or existed already
/*
if(newUser?.meta?.isNew === true) {
await locals.pb?.collection('users').update(
newUser['record']['id'], {
'freeTrial' : true,
'tier': 'Pro', //Give new users a free trial for the Pro Subscription
});
}
*/
} catch (err) {
console.log("Error logging in with OAuth2 user", err);
redirect(302, "/register");

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { numberOfUnreadNotification } from "$lib/store";
import { openLemonSqueezyUrl } from "$lib/lemonsqueezy";
//import Discount from '$lib/components/Discount.svelte';
import { onMount } from "svelte";

View File

@ -6,6 +6,45 @@ export const load = async ({ locals }) => {
redirect(303, "/login");
}
const getNotificationChannels = async () => {
const userId = user?.id;
let output = {};
try {
// Check if the user already exists in the collection
output = await pb.collection("notificationChannels").getFirstListItem(`user="${userId}"`);
} catch (error) {
if (error.status === 404) {
try {
// Fetch the collection schema to dynamically get all boolean fields
const collectionSchema = await pb.collection("notificationChannels").getFullList();
// Find all boolean fields and set them to `true`
const defaultValues = collectionSchema[0]
? Object.keys(collectionSchema[0]).reduce((acc, key) => {
if (typeof collectionSchema[0][key] === "boolean") {
acc[key] = true;
}
return acc;
}, {})
: {};
// Ensure the user field is included
defaultValues.user = userId;
// Create the new record
output = await pb.collection("notificationChannels").create(defaultValues);
} catch (creationError) {
console.error("Error creating new user notification channel:", creationError);
}
} else {
console.error("Error checking for existing user:", error);
}
}
return output; // Return only the latest item
};
const getPushSubscriptionData = async () => {
let output = {};
try {
@ -28,8 +67,6 @@ const getPushSubscriptionData = async () => {
};
const getSubscriptionData = async () => {
const output =
(
@ -45,9 +82,12 @@ const getPushSubscriptionData = async () => {
};
return {
getSubscriptionData: await getSubscriptionData(),
getPushSubscriptionData: await getPushSubscriptionData(),
getNotificationChannels: await getNotificationChannels(),
};
};

View File

@ -19,6 +19,14 @@
let nottifPermGranted: boolean | null = null;
let isPushSubscribed = data?.getPushSubscriptionData !== null ? true : false;
let notificationChannels = data?.getNotificationChannels;
const mode = Object?.entries(notificationChannels)
.filter(([key, value]) => typeof value === "boolean") // Filter boolean properties
.reduce((acc, [key, value]) => {
acc[key] = value; // Add to mode object
return acc;
}, {});
let subscriptionData = data?.getSubscriptionData;
let isClicked = false;
@ -199,6 +207,26 @@
}
loading = false;
}
async function updateNotificationChannels() {
const postData = {
id: notificationChannels?.id,
...mode,
};
const response = await fetch("/api/update-notification-channels", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
}
async function toggleMode(state) {
mode[state] = !mode[state];
await updateNotificationChannels();
}
</script>
<SEO
@ -251,13 +279,51 @@
>
</div>
{#if pwaInstalled}
<div
class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white"
>
<h2 class="text-white text-2xl font-semibold mb-3">
Push Notification
</h2>
<div
class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white"
>
<h2 class="text-white text-2xl font-semibold mb-3">Notification</h2>
Customize your notification alerts based on your preferences.
<div class="flex flex-col items-start w-full mt-4 mb-4">
<div class="flex w-full md:w-1/3 justify-between items-center">
<span class="text-white">Earnings Surprise</span>
<label class="inline-flex cursor-pointer relative">
<input
on:click={() => toggleMode("earningsSurprise")}
type="checkbox"
checked={mode["earningsSurprise"]}
value={mode["earningsSurprise"]}
class="sr-only peer"
/>
<div
class="w-10 h-5 bg-gray-600 rounded-full peer-checked:after:translate-x-4 peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[0.25rem] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-purple-500"
></div>
</label>
</div>
<div
class=" mt-2 flex w-full md:w-1/3 justify-between items-center"
>
<span class="text-white"> Why Priced Moved </span>
<label class="inline-flex cursor-pointer relative">
<input
on:click={() => toggleMode("wiim")}
type="checkbox"
checked={mode["wiim"]}
value={mode["wiim"]}
class="sr-only peer"
/>
<div
class="w-10 h-5 bg-gray-600 rounded-full peer-checked:after:translate-x-4 peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[0.25rem] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-purple-500"
></div>
</label>
</div>
</div>
<h3 class="text-white text-xl font-semibold mb-2 mt-4">
Push Notification
</h3>
{#if pwaInstalled}
<div class="mt-3">
{#if nottifPermGranted === null}
<p>Checking permissions...</p>
@ -302,8 +368,21 @@
</p>
{/if}
</div>
</div>
{/if}
{:else}
<div class="mt-2">
<p class="mb-3">
You can activate the push notification only if you downloaded
the app.
</p>
<label
for="installModal"
class="flex-none rounded px-4 py-1.5 text-sm font-semibold text-black cursor-pointer bg-[#fff] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900"
>
Install the App
</label>
</div>
{/if}
</div>
<div
class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white"
@ -670,3 +749,55 @@
</div>
</dialog>
<!-- End Cancel Subscription Modal -->
<!--Start Create Watchlist Modal-->
<input type="checkbox" id="installModal" class="modal-toggle" />
<dialog id="installModal" class="modal overflow-hidden p-3 sm:p-0">
<label for="installModal" class="cursor-pointer modal-backdrop"></label>
<div class="modal-box rounded w-full bg-secondary border border-gray-600">
<div class="flex flex-row items-center pt-5">
<h4 class="text-white text-2xl font-bold text-center m-auto">
Steps to install
</h4>
<label
for="installModal"
class="inline-block cursor-pointer absolute right-3 top-3 text-[1.3rem] sm:text-[1.8rem] text-white"
>
<svg
class="w-6 h-6 sm:w-8 sm:h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="white"
d="m6.4 18.308l-.708-.708l5.6-5.6l-5.6-5.6l.708-.708l5.6 5.6l5.6-5.6l.708.708l-5.6 5.6l5.6 5.6l-.708.708l-5.6-5.6z"
/></svg
>
</label>
</div>
<div
class="text-white flex flex-col justify-center items-center text-xl h-full"
>
<ul class="list-decimal list-inside text-left mt-5">
<li class="mb-2">Tap on the Safari share button.</li>
<li class="mb-2">Tap on "Add to Home Screen."</li>
<li class="mb-4">Tap on "Add."</li>
<p class="text-lg mb-4">
Note that web apps on iOS can only be installed using Safari.
</p>
</ul>
</div>
<div class="border-t border-gray-600 mt-2">
<label
for="installModal"
class="mt-4 font-semibold text-white text-xl m-auto flex justify-center"
>
Close
</label>
</div>
</div>
</dialog>

View File

@ -35,14 +35,7 @@ export const actions = {
//let username = generateUsername(formData.name.split(' ').join('')).toLowerCase();
try {
await locals.pb.collection("users").create(formData);
/*
await locals.pb?.collection('users').update(
newUser?.id, {
'freeTrial' : true,
'tier': 'Pro', //Give new users a free trial for the Pro Subscription
});
*/
const newUser = await locals.pb.collection("users").create(formData);
await locals.pb.collection("users").requestVerification(formData.email);
} catch (err) {