add notitication channels
This commit is contained in:
parent
cb1e41793c
commit
5da3db056d
@ -80,7 +80,7 @@
|
|||||||
<dialog id="installModal" class="modal overflow-hidden p-3 sm:p-0">
|
<dialog id="installModal" class="modal overflow-hidden p-3 sm:p-0">
|
||||||
<label for="installModal" class="cursor-pointer modal-backdrop"></label>
|
<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">
|
<div class="flex flex-row items-center pt-5">
|
||||||
<h4 class="text-white text-2xl font-bold text-center m-auto">
|
<h4 class="text-white text-2xl font-bold text-center m-auto">
|
||||||
Steps to install
|
Steps to install
|
||||||
|
|||||||
22
src/routes/api/update-notification-channels/+server.ts
Normal file
22
src/routes/api/update-notification-channels/+server.ts
Normal 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));
|
||||||
|
};
|
||||||
@ -1,10 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { formatDate } from "$lib/utils";
|
import { formatDate } from "$lib/utils";
|
||||||
import ArrowLogo from "lucide-svelte/icons/move-up-right";
|
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 HoverStockChart from "$lib/components/HoverStockChart.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { numberOfUnreadNotification } from "$lib/store";
|
import { numberOfUnreadNotification } from "$lib/store";
|
||||||
|
import SEO from "$lib/components/SEO.svelte";
|
||||||
|
import Infobox from "$lib/components/Infobox.svelte";
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
export let form;
|
export let form;
|
||||||
@ -57,35 +58,10 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<SEO
|
||||||
<title>
|
title="Stock Market Notifications | Real-Time Updates & Alerts"
|
||||||
{$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ""} Notifications
|
description="Stay informed with real-time stock market notifications. Get instant alerts on price changes, trends, and market movements to make smarter investment decisions."
|
||||||
· 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>
|
|
||||||
|
|
||||||
<section
|
<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"
|
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
|
<div
|
||||||
class="relative flex justify-center items-start overflow-hidden w-full"
|
class="relative flex justify-center items-start overflow-hidden w-full"
|
||||||
>
|
>
|
||||||
<main class="w-full lg:w-3/4 lg:pr-5">
|
<main class="w-full lg:w-3/4 lg:pr-10">
|
||||||
<div class="mb-6 border-b-[2px]">
|
<div class="mb-3 border-b-[2px]">
|
||||||
<h1 class="mb-1 text-white text-2xl sm:text-3xl font-bold">
|
<h1 class="mb-1 text-white text-2xl sm:text-3xl font-bold">
|
||||||
Notification
|
Notification
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<Infobox
|
||||||
|
text="Personalize your notifications on your account page."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if notificationList?.length !== 0}
|
{#if notificationList?.length !== 0}
|
||||||
<div class="flex flex-col items-start w-full text-white">
|
<div class="flex flex-col items-start w-full text-white">
|
||||||
{#each notificationList as item}
|
{#each notificationList as item}
|
||||||
{#if item?.notifyType === "priceAlert"}
|
{#if item?.notifyType === "priceAlert"}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<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]'
|
? 'bg-[#F9AB00] bg-opacity-[0.1]'
|
||||||
: ''} "
|
: ''} "
|
||||||
>
|
>
|
||||||
@ -163,7 +145,7 @@
|
|||||||
{:else if item?.notifyType === "wiim"}
|
{:else if item?.notifyType === "wiim"}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<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]'
|
? 'bg-[#F9AB00] bg-opacity-[0.1]'
|
||||||
: ''} "
|
: ''} "
|
||||||
>
|
>
|
||||||
@ -207,7 +189,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if item?.notifyType === "earningsSurprise"}
|
{:else if item?.notifyType === "earningsSurprise"}
|
||||||
<div
|
<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]'
|
? 'bg-[#F9AB00] bg-opacity-[0.1]'
|
||||||
: ''} "
|
: ''} "
|
||||||
>
|
>
|
||||||
|
|||||||
@ -47,18 +47,7 @@ export const GET = async ({ locals, url, cookies }) => {
|
|||||||
.authWithOAuth2Code(provider.name, code, expectedVerifier, redirectURL);
|
.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) {
|
} catch (err) {
|
||||||
console.log("Error logging in with OAuth2 user", err);
|
console.log("Error logging in with OAuth2 user", err);
|
||||||
redirect(302, "/register");
|
redirect(302, "/register");
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { numberOfUnreadNotification } from "$lib/store";
|
|
||||||
import { openLemonSqueezyUrl } from "$lib/lemonsqueezy";
|
import { openLemonSqueezyUrl } from "$lib/lemonsqueezy";
|
||||||
//import Discount from '$lib/components/Discount.svelte';
|
//import Discount from '$lib/components/Discount.svelte';
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|||||||
@ -6,6 +6,45 @@ export const load = async ({ locals }) => {
|
|||||||
redirect(303, "/login");
|
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 () => {
|
const getPushSubscriptionData = async () => {
|
||||||
let output = {};
|
let output = {};
|
||||||
try {
|
try {
|
||||||
@ -28,8 +67,6 @@ const getPushSubscriptionData = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getSubscriptionData = async () => {
|
const getSubscriptionData = async () => {
|
||||||
const output =
|
const output =
|
||||||
(
|
(
|
||||||
@ -45,9 +82,12 @@ const getPushSubscriptionData = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getSubscriptionData: await getSubscriptionData(),
|
getSubscriptionData: await getSubscriptionData(),
|
||||||
getPushSubscriptionData: await getPushSubscriptionData(),
|
getPushSubscriptionData: await getPushSubscriptionData(),
|
||||||
|
getNotificationChannels: await getNotificationChannels(),
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,6 +19,14 @@
|
|||||||
|
|
||||||
let nottifPermGranted: boolean | null = null;
|
let nottifPermGranted: boolean | null = null;
|
||||||
let isPushSubscribed = data?.getPushSubscriptionData !== null ? true : false;
|
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 subscriptionData = data?.getSubscriptionData;
|
||||||
let isClicked = false;
|
let isClicked = false;
|
||||||
@ -199,6 +207,26 @@
|
|||||||
}
|
}
|
||||||
loading = false;
|
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>
|
</script>
|
||||||
|
|
||||||
<SEO
|
<SEO
|
||||||
@ -251,13 +279,51 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if pwaInstalled}
|
|
||||||
<div
|
<div
|
||||||
class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white"
|
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">
|
<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
|
Push Notification
|
||||||
</h2>
|
</h3>
|
||||||
|
{#if pwaInstalled}
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
{#if nottifPermGranted === null}
|
{#if nottifPermGranted === null}
|
||||||
<p>Checking permissions...</p>
|
<p>Checking permissions...</p>
|
||||||
@ -302,8 +368,21 @@
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{: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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="mt-6 rounded border border-gray-600 p-4 text-base xs:p-4 xs:text-lg text-white"
|
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>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
<!-- End Cancel Subscription Modal -->
|
<!-- 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>
|
||||||
|
|||||||
@ -35,14 +35,7 @@ export const actions = {
|
|||||||
//let username = generateUsername(formData.name.split(' ').join('')).toLowerCase();
|
//let username = generateUsername(formData.name.split(' ').join('')).toLowerCase();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await locals.pb.collection("users").create(formData);
|
const newUser = 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
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
await locals.pb.collection("users").requestVerification(formData.email);
|
await locals.pb.collection("users").requestVerification(formData.email);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user