Skip to content

Commit

Permalink
Merge pull request #40 from Kashyap1ankit/fix/hero
Browse files Browse the repository at this point in the history
added notification functionality
  • Loading branch information
Kashyap1ankit authored Nov 24, 2024
2 parents fc3cc2a + 3fbc721 commit 9300d29
Show file tree
Hide file tree
Showing 124 changed files with 566 additions and 26 deletions.
153 changes: 153 additions & 0 deletions app/actions/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"use server";

import prisma from "@/db";
import { CheckUser } from "./users/checkUser";
import webpush from "web-push";

const vapidKeys = {
publicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY,
privateKey: process.env.VAPID_PRIVATE_KEY,
};

export async function addNotificationSubscription(subscription: string) {
try {
const response = await CheckUser();

if (response.status !== 200 || !response.userId)
throw new Error(response.message);

await prisma.notification.create({
data: {
userId: response.userId,
subscription,
},
});

return {
status: 200,
message: "Created subscription",
};
} catch (error) {
return {
status: 400,
message: (error as Error).message,
};
}
}

export async function removeNotificationSubscription() {
try {
const response = await CheckUser();

if (response.status !== 200 || !response.userId)
throw new Error(response.message);

const findPost = await prisma.notification.findFirst({
where: {
userId: response.userId,
},
select: {
id: true,
},
});

if (!findPost) throw new Error("No such Notification subscription");

await prisma.notification.delete({
where: {
id: findPost?.id,
userId: response.userId,
},
});

return {
status: 200,
message: "Deleted subscription",
};
} catch (error) {
return {
status: 400,
message: (error as Error).message,
};
}
}

export const getUserSubscription = async () => {
const response = await CheckUser();

if (response.status !== 200 || !response.userId)
throw new Error(response.message);

try {
const res = await prisma.notification.findFirst({
where: {
userId: response.userId,
},
});
return {
status: 200,
message: "Subscription Retrieved",
data: res,
};
} catch (error) {
return {
status: 400,
message: (error as Error).message,
data: null,
};
}
};

export async function SendNotification(company_name: string) {
try {
const response = await CheckUser();

if (response.status !== 200 || !response.userId)
throw new Error(response.message);

const sub = await prisma.notification.findFirst({
where: {
userId: response.userId,
},
});

if (!sub) throw new Error("No subscription found");

webpush.setVapidDetails(
"mailto:[email protected]",
vapidKeys.publicKey as string,
vapidKeys.privateKey as string,
);

const allSubscribedUser = await prisma.notification.findMany({
where: {
userId: {
not: response.userId,
},
},
});

if (allSubscribedUser.length < 0)
throw new Error("No user to send notification");

for (const user of allSubscribedUser) {
await webpush.sendNotification(
JSON.parse(user.subscription),
JSON.stringify({
message: "New Job Alert",
body: `🔥Vacany at ${company_name}...Apply Fast`,
}),
);
}

return {
status: 200,
message: "Notification Sent",
};
} catch (error) {
return {
status: 400,
message: (error as Error).message,
};
}
}
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const AuthProvider = lazy(() => import("@/provider"));
import { auth } from "@/auth";
import { Toaster } from "@/components/ui/sonner";
import Loader from "./loading";
import RegisterSw from "@/components/RegisterSw";

const inter = Inter({ subsets: ["latin"] });

Expand All @@ -29,6 +30,7 @@ export default async function RootLayout({
<div className="overflow-x-hidden bg-primaryBg">
{children}
<Toaster />
<RegisterSw />
</div>
</AuthProvider>
</Suspense>
Expand Down
182 changes: 182 additions & 0 deletions components/GetNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"use client";
import {
addNotificationSubscription,
getUserSubscription,
removeNotificationSubscription,
} from "@/app/actions/notification";

import { useEffect, useState } from "react";
import { IoNotifications, IoNotificationsOff } from "react-icons/io5";
import { toast } from "sonner";

function urlB64ToUint8Array(base64String: string): Uint8Array {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
// eslint-disable-next-line no-useless-escape
.replace(/\-/g, "+")
.replace(/_/g, "/");
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
export default function GetNotification() {
const [notificationPermission, setNotificationPermission] = useState<
"denied" | "granted" | "default"
>(window.Notification.permission);

const [refetch, setRefetch] = useState<boolean>(false);

const [userSub, setUserSub] = useState<{
id: string;
userId: string;
subscription: string;
} | null>(null);

async function showNotificationClick() {
if ("serviceWorker" in navigator && "Notification" in window) {
try {
await window.Notification.requestPermission();
if (window.Notification.permission === "granted") {
setNotificationPermission("granted");
await verifyRegistration();
} else {
setNotificationPermission(window.Notification.permission);
toast.info(
"Permission Not granted . Go to settings & allow notifications",
);
}
} catch (error) {
toast((error as Error).message);
} finally {
setRefetch((prev) => !prev);
}
}
}

async function verifyRegistration() {
try {
if ("serviceWorker" in navigator) {
let registration = await navigator.serviceWorker.getRegistration();

if (!registration) {
registration = await navigator.serviceWorker.register("/sw.js", {
scope: "/",
updateViaCache: "none",
});
}

await subscribeUser(registration);
}
} catch (error) {
toast((error as Error).message);
}
}

async function subscribeUser(registration: ServiceWorkerRegistration) {
try {
console.log(registration);
if (!("Notification" in window)) {
throw new Error("This browser does not support notifications");
}

const permission = await Notification.requestPermission();
if (permission !== "granted") {
throw new Error("Notification permission denied");
}

if (!("PushManager" in window)) {
throw new Error("Push messaging is not supported");
}

const appServeKey = urlB64ToUint8Array(
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
);

const option = {
userVisibleOnly: true,
applicationServerKey: appServeKey,
};

const subcriptionObject =
await registration.pushManager.subscribe(option);
const response = await addNotificationSubscription(
JSON.stringify(subcriptionObject),
);

if (response.status !== 200) throw new Error(response.message);

toast.success("Notification Is enabled", {
style: {
backgroundColor: "#65a30d",
color: "white",
borderColor: "#65a30d",
},
});
} catch (error) {
toast.error((error as Error).message, {
style: {
backgroundColor: "red",
color: "white",
borderColor: "#334155",
},
});
}
}

async function deleteNotificationPermission() {
if ("serviceWorker" in navigator && "Notification" in window) {
try {
await removeNotificationSubscription();
setNotificationPermission("denied");
toast.warning(
"Notifications have been disabled. To stop browser notifications completely, please update your notification settings in the browser",
{
style: {
backgroundColor: "#334155",
color: "white",
borderColor: "#334155",
},
},
);
} catch (error) {
toast((error as Error).message);
}
}
}

useEffect(() => {
setNotificationPermission(window.Notification.permission);
}, []);

useEffect(() => {
const getUserSubs = async () => {
const response = await getUserSubscription();

if (response.status !== 200) {
setUserSub(null);
return;
}
setUserSub(response.data);
};
getUserSubs();
}, [refetch]);

return (
<div>
{notificationPermission === "granted" && userSub?.id ? (
<IoNotifications
className="size-6 cursor-pointer text-white"
onClick={deleteNotificationPermission}
/>
) : (
<IoNotificationsOff
className="size-6 cursor-pointer text-white"
onClick={showNotificationClick}
/>
)}
</div>
);
}
11 changes: 10 additions & 1 deletion components/Job/Create/CreateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { SendNotification } from "@/app/actions/notification";

export default function CreateForm() {
const {
Expand Down Expand Up @@ -76,7 +77,15 @@ export default function CreateForm() {
data.company_logo = uploadedImageUrl.secure_url;
const response = await CreateJob(data);
if (response.status !== 200) throw new Error(response.message);
toast("Successfully created");
toast.success("Successfully created", {
style: {
backgroundColor: "#65a30d",
color: "white",
borderColor: "#65a30d",
},
});

await SendNotification(data.company);
router.push("/jobs");
} catch (error) {
toast((error as Error).message);
Expand Down
Loading

0 comments on commit 9300d29

Please sign in to comment.