This commit is contained in:
zadit
2024-10-22 16:05:30 +07:00
parent e2522bd91c
commit 1ecc6db645
17 changed files with 320 additions and 137 deletions

View File

@@ -315,62 +315,73 @@ function App() {
navigate({ search: queryParams.toString() }, { replace: true }); navigate({ search: queryParams.toString() }, { replace: true });
} }
}; };
// useEffect(() => {
// const askNotificationPermission = async () => {
// let permission = Notification.permission;
// // Check current permission
// if (permission === "default") {
// setModal("req_notification");
// // Request permission and wait for the result
// permission = await Notification.requestPermission();
// }
// // Handle permission results
// if (permission === "granted") {
// await resetNotificationSubscription();
// closeModal(["req_notification", "denied_notification"]);
// } else if (permission === "denied") {
// setModal("blocked_notification");
// console.error("Notification permission denied.");
// }
// };
// const handleTransactionConfirmed = async (data) => {
// console.log("transaction notification", data);
// await askNotificationPermission();
// setModal("transaction_success", data);
// };
// // Add socket listener for transaction confirmations
// socket.on("transaction_success", handleTransactionConfirmed);
// // Cleanup the socket listener on component unmount
// return () => {
// socket.off("transaction_success", handleTransactionConfirmed);
// };
// }, [user]);
useEffect(() => { useEffect(() => {
const askNotificationPermission = async () => { const startPermissionPolling = async () => {
let permission = Notification.permission; const checkInterval = 5000; // Check every 5 seconds
// Check current permission const intervalId = setInterval(async () => {
if (permission === "default") { const permission = Notification.permission;
setModal("req_notification");
// Request permission and wait for the result
permission = await Notification.requestPermission();
}
// If permission is already granted, reset subscriptions
if (permission === "granted") { if (permission === "granted") {
await resetNotificationSubscription(); await resetNotificationSubscription();
closeModal(["req_notification", "denied_notification"]); clearInterval(intervalId); // Stop checking once subscribed
} else if (permission === "denied") { } else if (permission === "denied") {
setModal("blocked_notification");
console.error("Notification permission denied."); console.error("Notification permission denied.");
} }
}, checkInterval);
// Continuously check until permission is granted
while (permission !== "granted") {
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for 1 second
const permissionn = Notification.permission;
if (permissionn === "granted") {
closeModal(["req_notification", "denied_notification"]);
await resetNotificationSubscription();
break;
} else if (permissionn === "denied") {
setModal("blocked_notification");
console.error("Notification permission denied.");
break;
}
}
}; };
const handleLoad = async () => {
const ses = sessionStorage.getItem("notifAsk");
if (!ses && user != null && (user.roleId < 3 || user.roleId > 2)) { startPermissionPolling();
await askNotificationPermission(); }, []);
sessionStorage.setItem("notifAsk", true);
} useEffect(() => {
};
handleLoad();
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
window.addEventListener("load", handleLoad); navigator.serviceWorker.register("/service-worker.js")
.then(registration => {
// Cleanup the event listener on component unmount console.log("Service Worker registered with scope:", registration.scope);
return () => { })
window.removeEventListener("load", handleLoad); .catch(error => {
}; console.error("Service Worker registration failed:", error);
});
} }
}, [user]); }, []);
return ( return (
<div className="App"> <div className="App">
@@ -524,6 +535,7 @@ function App() {
isOpen={isModalOpen} isOpen={isModalOpen}
modalContent={modalContent} modalContent={modalContent}
onClose={closeModal} onClose={closeModal}
setModal={setModal}
/> />
</div> </div>
); );

View File

@@ -20,8 +20,9 @@
border: none; border: none;
margin: 0 auto; margin: 0 auto;
cursor: pointer; cursor: pointer;
display: block; /* Centering the button */ align-items: center;
text-align: center; text-align: center;
display: inline-flex;
} }
.replica { .replica {
@@ -59,6 +60,7 @@
transition: all 0.5s ease-in-out; transition: all 0.5s ease-in-out;
font-size: 3vw; font-size: 3vw;
text-align: center; text-align: center;
pointer-events: none;
} }
.bussinessName.active { .bussinessName.active {

View File

@@ -8,6 +8,7 @@ import jsqr from "jsqr";
const ButtonWithReplica = ({ const ButtonWithReplica = ({
children, children,
price, price,
disabled,
paymentUrl, paymentUrl,
handleClick, handleClick,
Open, Open,
@@ -131,6 +132,7 @@ const ButtonWithReplica = ({
<button <button
className="button" className="button"
onClick={() => (isPaymentOpen ? handleClick() : handleOpen())} onClick={() => (isPaymentOpen ? handleClick() : handleOpen())}
disabled = {disabled}
> >
{children} {children}
</button> </button>
@@ -138,6 +140,7 @@ const ButtonWithReplica = ({
<div className={`replica ${isActive ? "active" : ""}`}></div> <div className={`replica ${isActive ? "active" : ""}`}></div>
<QRCodeSVG <QRCodeSVG
className={`bussinessQR ${isActive ? "active" : ""}`} className={`bussinessQR ${isActive ? "active" : ""}`}
style={{pointerEvents: 'none'}}
bgColor={"transparent"} bgColor={"transparent"}
fgColor={fgColor} fgColor={fgColor}
value={QRValue} value={QRValue}

View File

@@ -309,7 +309,7 @@ const Header = ({
</Title> </Title>
<div style={{ visibility: showProfile ? "visible" : "hidden" }}> <div style={{ visibility: showProfile ? "visible" : "hidden" }}>
<ProfileImage <ProfileImage
src={shopImage} src={shopImage || "https://static-00.iconduck.com/assets.00/profile-major-icon-1024x1024-9rtgyx30.png"}
alt="Profile" alt="Profile"
onClick={handleImageClick} onClick={handleImageClick}
animate={showRectangle && animate} animate={showRectangle && animate}

View File

@@ -205,7 +205,7 @@ const Item = ({
<div className={styles.itemQty}> <div className={styles.itemQty}>
<button <button
className={styles.addButton} className={styles.addButton}
style={{ backgroundColor: !isAvailable ? "gray" : "inherit" }} style={{ backgroundColor: !isAvailable ? "" : "inherit", border: `2px solid ${isAvailable? 'inherit': 'gray'}`, color: `${isAvailable? '#73a585': 'gray'}`}}
onClick={handlePlusClick} onClick={handlePlusClick}
disabled={!isAvailable} // Optionally disable the button if not available disabled={!isAvailable} // Optionally disable the button if not available
> >
@@ -217,7 +217,7 @@ const Item = ({
<button <button
className={styles.addButton} className={styles.addButton}
style={{ style={{
backgroundColor: "#4da94d", backgroundColor: "white",
width: "150px", width: "150px",
}} }}
onClick={isBeingEdit ? handleUpdate : handleCreate} onClick={isBeingEdit ? handleUpdate : handleCreate}

View File

@@ -532,7 +532,7 @@ const ItemLister = ({
onClick={toggleAddNewItem} onClick={toggleAddNewItem}
style={{ display: "inline-block" }} style={{ display: "inline-block" }}
> >
cancel
</button> </button>
<Item blank={true} handleCreateItem={onCreateItem} /> <Item blank={true} handleCreateItem={onCreateItem} />
</> </>
@@ -549,7 +549,7 @@ const ItemLister = ({
onClick={() => editItem(0)} onClick={() => editItem(0)}
style={{ display: "inline-block" }} style={{ display: "inline-block" }}
> >
cancel
</button> </button>
)} )}
<div className={styles["itemWrapper"]}> <div className={styles["itemWrapper"]}>
@@ -601,7 +601,7 @@ const ItemLister = ({
onClick={() => editItem(0)} onClick={() => editItem(0)}
style={{ display: "inline-block" }} style={{ display: "inline-block" }}
> >
cancel
</button> </button>
)} )}
<div className={styles["itemWrapper"]}> <div className={styles["itemWrapper"]}>

View File

@@ -16,7 +16,8 @@ import MaterialMutationsPage from "../pages/MaterialMutationsPage.js";
import Reports from "../pages/Reports.js"; import Reports from "../pages/Reports.js";
import NotificationBlocked from "../pages/NotificationBlocked.js"; import NotificationBlocked from "../pages/NotificationBlocked.js";
import WelcomePageEditor from "../pages/WelcomePageEditor.js"; import WelcomePageEditor from "../pages/WelcomePageEditor.js";
const Modal = ({ shop, isOpen, onClose, modalContent }) => { import GuidePage from "../pages/GuidePage";
const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => {
if (!isOpen) return null; if (!isOpen) return null;
// Function to handle clicks on the overlay // Function to handle clicks on the overlay
@@ -33,9 +34,6 @@ const Modal = ({ shop, isOpen, onClose, modalContent }) => {
return ( return (
<div onClick={handleOverlayClick} className={styles.modalOverlay}> <div onClick={handleOverlayClick} className={styles.modalOverlay}>
<div className={styles.modalContent} onClick={handleContentClick}> <div className={styles.modalContent} onClick={handleContentClick}>
<button onClick={() => onClose()} className={styles.closeButton}>
&times;
</button>
{modalContent === "req_notification" && <NotificationBlocked />} {modalContent === "req_notification" && <NotificationBlocked />}
{modalContent === "blocked_notification" && <NotificationBlocked />} {modalContent === "blocked_notification" && <NotificationBlocked />}
{modalContent === "create_clerk" && <CreateClerk shopId={shop.cafeId} />} {modalContent === "create_clerk" && <CreateClerk shopId={shop.cafeId} />}
@@ -53,7 +51,11 @@ const Modal = ({ shop, isOpen, onClose, modalContent }) => {
{modalContent === "payment_claimed" && ( {modalContent === "payment_claimed" && (
<Payment_claimed paymentUrl={shop.qrPayment} /> <Payment_claimed paymentUrl={shop.qrPayment} />
)} )}
{modalContent === "transaction_success" && <Transaction_success />}
{modalContent === "create_item" && (
<GuidePage guideType={'create_item'} />
)}
{modalContent === "transaction_success" && <Transaction_success setModal={setModal}/>}
{modalContent === "transaction_end" && <Transaction_end />} {modalContent === "transaction_end" && <Transaction_end />}
{modalContent === "transaction_failed" && <Transaction_failed />} {modalContent === "transaction_failed" && <Transaction_failed />}
{modalContent === "payment_option" && ( {modalContent === "payment_option" && (

View File

@@ -29,6 +29,7 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
const [backgroundImage, setBackgroundImage] = useState(""); const [backgroundImage, setBackgroundImage] = useState("");
const [canvaz, setCanvaz] = useState('');
const [videoSrc, setVideoSrc] = useState(''); const [videoSrc, setVideoSrc] = useState('');
const videoRef = useRef(null); const videoRef = useRef(null);
@@ -152,8 +153,10 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
// useEffect for setting up the socket listener // useEffect for setting up the socket listener
useEffect(() => { useEffect(() => {
const handleUpdateCanvas = (response) => { const handleUpdateCanvas = (response) => {
if (response) { if (response && response !== canvaz) {
console.log(response);
console.log(canvaz);
setCanvaz(response);
fetch(response) fetch(response)
.then((response) => response.blob()) .then((response) => response.blob())
.then((blob) => { .then((blob) => {
@@ -165,8 +168,8 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
} }
}) })
.catch((error) => console.error('Error loading video:', error)); .catch((error) => console.error('Error loading video:', error));
} } else if (!response) {
else{ // Clear the video source if response is empty
setVideoSrc(''); setVideoSrc('');
if (videoRef.current) { if (videoRef.current) {
videoRef.current.load(); // Reload the video element videoRef.current.load(); // Reload the video element
@@ -181,7 +184,7 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
return () => { return () => {
socket.off("updateCanvas", handleUpdateCanvas); socket.off("updateCanvas", handleUpdateCanvas);
}; };
}, [socket]); }, [socket, canvaz]);
useEffect(() => { useEffect(() => {
// Simulate progress every 100ms // Simulate progress every 100ms

View File

@@ -253,7 +253,7 @@ export const deleteCafeOwner = async (shopId, email, username, password) => {
} }
}; };
export const createClerks = async (shopId, email, username, password) => { export const createClerks = async (shopId, username, password) => {
const token = getLocalStorage("auth"); const token = getLocalStorage("auth");
if (token) { if (token) {
try { try {
@@ -266,7 +266,6 @@ export const createClerks = async (shopId, email, username, password) => {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
email: email,
username: username, username: username,
password: password, password: password,
}), }),

View File

@@ -20,7 +20,7 @@ import Header from "../components/Header";
import { ThreeDots } from "react-loader-spinner"; import { ThreeDots } from "react-loader-spinner";
import { updateLocalStorage, removeLocalStorage } from "../helpers/localStorageHelpers"; import { getLocalStorage, updateLocalStorage, removeLocalStorage } from "../helpers/localStorageHelpers";
import { unsubscribeUser } from "../helpers/subscribeHelpers.js"; import { unsubscribeUser } from "../helpers/subscribeHelpers.js";
import WelcomePage from "./WelcomePage.js"; import WelcomePage from "./WelcomePage.js";
@@ -94,8 +94,29 @@ function CafePage({
// Navigate to the new cafeId while keeping existing params // Navigate to the new cafeId while keeping existing params
navigate(`/${user.cafeId}?${currentParams}`, { replace: true }); navigate(`/${user.cafeId}?${currentParams}`, { replace: true });
} }
}, [user, shopId]); }, [user, shopId]);
useEffect(() => {
function fetchData() {
console.log(user.userId == shopOwnerId)
setModal("create_item");
}
console.log(getLocalStorage('auth'))
if (getLocalStorage("auth") != null) {
const executeFetch = async () => {
while (user.length == 0) {
await new Promise((resolve) => setTimeout(resolve, 100)); // Wait until the user is not null
}
console.log(user)
console.log('open')
if (user.length != 0 && user.userId == shopOwnerId && shopItems.length == 0) fetchData();
};
executeFetch();
}
}, [user, shopItems]);
useEffect(() => { useEffect(() => {
if (token) { if (token) {
updateLocalStorage("auth", token); updateLocalStorage("auth", token);

View File

@@ -2,7 +2,6 @@ import React, { useState } from 'react';
import { createClerks } from '../helpers/userHelpers'; // Adjust the import path as needed import { createClerks } from '../helpers/userHelpers'; // Adjust the import path as needed
const CreateClerk = ({ shopId }) => { const CreateClerk = ({ shopId }) => {
const [email, setEmail] = useState('');
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -14,17 +13,17 @@ const CreateClerk = ({ shopId }) => {
setMessage(''); setMessage('');
// Basic validation // Basic validation
if (!email || !username || !password) { if (!username || !password) {
setMessage('All fields are required'); setMessage('Username and password are required');
setLoading(false); setLoading(false);
return; return;
} }
try { try {
const create = await createClerks(shopId, email, username, password); const create = await createClerks(shopId, username, password);
if (create) setMessage('Clerk created successfully'); if (create) setMessage('Clerk created successfully');
else setMessage('failed') else setMessage('Failed to create clerk');
} catch (error) { } catch (error) {
setMessage('Error creating clerk'); setMessage('Error creating clerk');
} finally { } finally {
@@ -36,13 +35,6 @@ const CreateClerk = ({ shopId }) => {
<div style={styles.container}> <div style={styles.container}>
<h2 style={styles.header}>Create Clerk</h2> <h2 style={styles.header}>Create Clerk</h2>
<form onSubmit={handleSubmit} style={styles.form}> <form onSubmit={handleSubmit} style={styles.form}>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
style={styles.input}
/>
<input <input
type="text" type="text"
placeholder="Username" placeholder="Username"
@@ -60,24 +52,33 @@ const CreateClerk = ({ shopId }) => {
<button type="submit" style={styles.button} disabled={loading}> <button type="submit" style={styles.button} disabled={loading}>
{loading ? 'Creating...' : 'Create Clerk'} {loading ? 'Creating...' : 'Create Clerk'}
</button> </button>
{message && <p style={styles.message}>{message}</p>} {message && (
<p style={{ ...styles.message, color: message.includes('success') ? 'green' : 'red' }}>
{message}
</p>
)}
</form> </form>
</div> </div>
); );
}; };
// Basic styling to make it mobile-friendly // Basic styling to make it mobile-friendly with a white background
const styles = { const styles = {
container: { container: {
backgroundColor: '#fff',
width: '100%', width: '100%',
maxWidth: '400px', maxWidth: '350px',
margin: '0 auto', margin: '0 auto',
padding: '20px', padding: '20px',
boxShadow: '0 4px 10px rgba(0, 0, 0, 0.1)',
borderRadius: '8px',
boxSizing: 'border-box', boxSizing: 'border-box',
}, },
header: { header: {
textAlign: 'center', textAlign: 'center',
marginBottom: '20px', marginBottom: '20px',
fontSize: '20px',
color: '#333',
}, },
form: { form: {
display: 'flex', display: 'flex',
@@ -85,25 +86,26 @@ const styles = {
gap: '15px', gap: '15px',
}, },
input: { input: {
padding: '10px', padding: '12px',
fontSize: '16px', fontSize: '16px',
borderRadius: '5px', borderRadius: '8px',
border: '1px solid #ccc', border: '1px solid #ccc',
width: '100%', width: '100%',
boxSizing: 'border-box', boxSizing: 'border-box',
backgroundColor: '#f9f9f9',
}, },
button: { button: {
padding: '10px', padding: '12px',
fontSize: '16px', fontSize: '16px',
borderRadius: '5px', borderRadius: '8px',
border: 'none', border: 'none',
backgroundColor: '#28a745', backgroundColor: '#28a745',
color: 'white', color: 'white',
cursor: 'pointer', cursor: 'pointer',
width: '100%',
}, },
message: { message: {
textAlign: 'center', textAlign: 'center',
color: 'red',
marginTop: '10px', marginTop: '10px',
}, },
}; };

View File

@@ -108,7 +108,7 @@ const Dashboard = ({ user, setModal }) => {
className={styles.rectangle} className={styles.rectangle}
onClick={() => setIsCreating(true)} onClick={() => setIsCreating(true)}
> >
Create Admin Create Client
</div> </div>
) : ( ) : (
<div <div

28
src/pages/GuidePage.css Normal file
View File

@@ -0,0 +1,28 @@
/* GuidePage.css */
.guide-page {
padding: 16px;
font-size: 16px;
line-height: 1.5;
background-color: #f9f9f9;
color: #333;
max-width: 600px;
margin: 0 auto;
}
h2 {
font-size: 24px;
margin-bottom: 12px;
}
p {
margin-bottom: 16px;
}
.guide-video {
width: 100%;
max-width: 100%;
height: auto;
border-radius: 8px;
margin-top: 16px;
}

54
src/pages/GuidePage.js Normal file
View File

@@ -0,0 +1,54 @@
import React from 'react';
import './GuidePage.css';
const GuidePage = ({ guideType }) => {
const renderGuideContent = () => {
switch (guideType) {
case 'create_item':
return (
<div>
<h2>Setup Guide</h2>
<p>1. Turn on edit mode and create item type</p>
<video
src="https://api.kedaimaster.com/uploads/create_item_guide_1.mkv"
autoPlay
muted
loop
className="guide-video"
/>
</div>
);
case 'troubleshooting':
return (
<div>
<h2>Troubleshooting Guide</h2>
<p>Follow these steps to troubleshoot common issues...</p>
{/* Add more troubleshooting details here */}
</div>
);
case 'features':
return (
<div>
<h2>Features Guide</h2>
<p>Learn about the different features available...</p>
{/* Add more feature details here */}
</div>
);
default:
return (
<div>
<h2>Welcome to the Guide</h2>
<p>Please select a guide type to get started.</p>
</div>
);
}
};
return (
<div className="guide-page">
{renderGuideContent()}
</div>
);
};
export default GuidePage;

View File

@@ -38,12 +38,24 @@ export default function Transactions({
try { try {
const fetchedTransaction = await getTransaction(transactionId); const fetchedTransaction = await getTransaction(transactionId);
setTransaction(fetchedTransaction); setTransaction(fetchedTransaction);
console.log(transaction); console.log(fetchedTransaction); // Log the fetched transaction
} catch (error) { } catch (error) {
console.error("Error fetching transaction:", error); console.error("Error fetching transaction:", error);
} }
}; };
fetchData();
const waitForLocalStorage = async () => {
while (localStorage.getItem("auth") === null) {
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
}
};
const initialize = async () => {
await waitForLocalStorage();
await fetchData();
};
initialize();
}, [searchParams]); }, [searchParams]);
useEffect(() => { useEffect(() => {
@@ -189,14 +201,17 @@ export default function Transactions({
price={ price={
"Rp" + calculateTotalPrice(transaction.DetailedTransactions) "Rp" + calculateTotalPrice(transaction.DetailedTransactions)
} }
disabled={isPaymentLoading} disabled={transaction.payment_type == 'cash' || isPaymentLoading}
isPaymentLoading={isPaymentLoading} isPaymentLoading={isPaymentLoading}
handleClick={() => handleConfirm(transaction.transactionId)} handleClick={() => handleConfirm(transaction.transactionId)}
Open={() => setIsPaymentOpen(true)} Open={() => setIsPaymentOpen(true)}
isPaymentOpen={isPaymentOpen} isPaymentOpen={isPaymentOpen}
> >
{isPaymentLoading ? ( {transaction.payment_type == 'cash' || isPaymentLoading ? (
<>
{transaction.payment_type == 'cash' && <p>tunggu konfirmasi</p>}
<ColorRing height="50" width="50" color="white" /> <ColorRing height="50" width="50" color="white" />
</>
) : isPaymentOpen ? ( ) : isPaymentOpen ? (
"Claim has paid" // Display "Confirm has paid" if the transaction is confirmed (1) "Claim has paid" // Display "Confirm has paid" if the transaction is confirmed (1)
) : ( ) : (

View File

@@ -1,28 +1,56 @@
import React from "react"; import React from "react";
import { ColorRing } from "react-loader-spinner";
import styles from "./Transactions.module.css"; import styles from "./Transactions.module.css";
import { requestNotificationPermission } from '../services/notificationService'; // Import the notification service
export default function Transaction_pending() { export default function Transaction_pending({ setModal }) {
const containerStyle = { // const containerStyle = {
display: "flex", // display: "flex",
justifyContent: "center", // justifyContent: "center",
alignItems: "center", // alignItems: "center",
width: "100%", // width: "100%",
height: "100%", // This makes the container stretch to the bottom of the viewport // height: "100%",
backgroundColor: "#000", // Optional: Set a background color if you want to see the color ring clearly // backgroundColor: "",
// };
const handleNotificationClick = async () => {
const permission = await requestNotificationPermission();
if (permission === "granted") {
console.log("Notification permission granted.");
// Set up notifications or show a success modal
} else if (permission === "denied") {
console.error("Notification permission denied.");
setModal('blocked_notification'); // Show modal for blocked notifications
}
}; };
return ( return (
<div className={styles.Transactions}> <div className={styles.Transactions}>
<div className={containerStyle}>
<div style={{ marginTop: "30px", textAlign: "center" }}> <div style={{ marginTop: "30px", textAlign: "center" }}>
<h2>transaction success</h2> <h2>Transaction Success</h2>
<img <img
className={styles.expression} className={styles.expression}
src="https://i.imgur.com/sgvMI02.pngs" src="https://i.imgur.com/sgvMI02.png"
alt="Success" alt="Success"
/> />
</div> <p style={{ marginTop: "20px", color: "white" }}>
Do you want to get notifications when your item is ready?
</p>
<button
onClick={handleNotificationClick}
style={{
marginTop: "10px",
padding: "10px 20px",
fontSize: "16px",
cursor: "pointer",
backgroundColor: "#4CAF50",
color: "#fff",
border: "none",
borderRadius: "5px",
}}
>
yes
</button>
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,14 @@
// notificationService.js
export const requestNotificationPermission = async () => {
let permission = Notification.permission;
// Check current permission
if (permission === "default") {
const result = await Notification.requestPermission();
permission = result; // Update permission status
}
// Return permission status
return permission;
};