688 lines
22 KiB
JavaScript
688 lines
22 KiB
JavaScript
import React, { useState, useEffect, useRef } from "react";
|
|
import "./App.css";
|
|
import "./components/Loading.css";
|
|
import {
|
|
BrowserRouter as Router,
|
|
Route,
|
|
Routes,
|
|
useNavigate,
|
|
useLocation,
|
|
} from "react-router-dom";
|
|
import socket from "./services/socketService";
|
|
|
|
|
|
import Dashboard from "./pages/Dashboard";
|
|
import ScanMeja from "./pages/ScanMeja";
|
|
import CafePage from "./pages/CafePage";
|
|
import SearchResult from "./pages/SearchResult";
|
|
import Cart from "./pages/Cart";
|
|
import Invoice from "./pages/Invoice";
|
|
import Transactions from "./pages/Transactions";
|
|
import Footer from "./components/Footer";
|
|
|
|
import GuestSideLogin from "./pages/GuestSideLogin";
|
|
import GuestSide from "./pages/GuestSide";
|
|
import { getItemTypesWithItems } from "./helpers/itemHelper.js";
|
|
import { getTableByCode } from "./helpers/tableHelper.js";
|
|
|
|
import {
|
|
getTransactionsFromCafe,
|
|
} from "./helpers/transactionHelpers";
|
|
import {
|
|
getConnectedGuestSides,
|
|
getClerks,
|
|
removeConnectedGuestSides,
|
|
} from "./helpers/userHelpers.js";
|
|
import {
|
|
getLocalStorage,
|
|
removeLocalStorage,
|
|
} from "./helpers/localStorageHelpers";
|
|
import { calculateTotals } from "./helpers/cartHelpers";
|
|
import {
|
|
subscribeUser,
|
|
resetNotificationSubscription,
|
|
} from "./helpers/subscribeHelpers.js";
|
|
import Modal from "./components/Modal"; // Import your modal component
|
|
|
|
function App() {
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const [user, setUser] = useState([]);
|
|
const [shopClerks, setShopClerks] = useState([]);
|
|
const [guestSideOfClerk, setGuestSideOfClerk] = useState(null);
|
|
const [guestSides, setGuestSides] = useState([]);
|
|
const [shopId, setShopId] = useState("");
|
|
const [shopIdentifier, setShopIdentifier] = useState("");
|
|
const [table, setTable] = useState([]);
|
|
const [totalItemsCount, setTotalItemsCount] = useState(0);
|
|
const [deviceType, setDeviceType] = useState("");
|
|
const [shop, setShop] = useState([]);
|
|
const [shopItems, setShopItems] = useState([]);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [modalContent, setModalContent] = useState(null);
|
|
const [onModalCloseFunction, setOnModalCloseFunction] = useState(null);
|
|
const transactionList = useRef(null);
|
|
const [queue, setQueue] = useState([]);
|
|
|
|
|
|
const validTransactionStates = [
|
|
'new_transaction',
|
|
'transaction_canceled',
|
|
'transaction_pending',
|
|
'transaction_confirmed',
|
|
'payment_claimed',
|
|
'transaction_success',
|
|
'transaction_end',
|
|
'transaction_failed',
|
|
];
|
|
|
|
useEffect(() => {
|
|
const calculateTotalsFromLocalStorage = () => {
|
|
const { totalCount } = calculateTotals(shopId);
|
|
setTotalItemsCount(totalCount);
|
|
};
|
|
|
|
calculateTotalsFromLocalStorage();
|
|
|
|
const handleStorageChange = () => {
|
|
calculateTotalsFromLocalStorage();
|
|
};
|
|
|
|
window.addEventListener("localStorageUpdated", handleStorageChange);
|
|
|
|
return () => {
|
|
window.removeEventListener("localStorageUpdated", handleStorageChange);
|
|
};
|
|
}, [shopId]);
|
|
|
|
const handleSetParam = async ({ shopIdentifier, tableCode }) => {
|
|
setShopIdentifier(shopIdentifier);
|
|
|
|
if (tableCode)
|
|
if (table.length == 0) {
|
|
const gettable = await getTableByCode(shopIdentifier, tableCode);
|
|
if (gettable) setTable(gettable);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
console.log("gettingItems");
|
|
try {
|
|
const { response, cafe, data } = await getItemTypesWithItems(shopIdentifier);
|
|
if (response.status === 200) {
|
|
setShopId(cafe.cafeId)
|
|
setShop(cafe);
|
|
setShopItems(data);
|
|
console.log(data)
|
|
// Filter out unavailable items
|
|
const filteredData = data
|
|
.map((itemType) => ({
|
|
...itemType,
|
|
itemList: itemType.itemList.filter((item) => item.availability),
|
|
}))
|
|
.filter((itemType) => itemType.itemList.length > 0); // Remove empty itemTypes
|
|
|
|
// Update local storage by removing unavailable items
|
|
const updatedLocalStorage =
|
|
JSON.parse(localStorage.getItem("cart")) || [];
|
|
const newLocalStorage = updatedLocalStorage.map((cafee) => {
|
|
if (cafee.cafeId === cafe.cafeId) {
|
|
return {
|
|
...cafee,
|
|
items: cafee.items.filter((item) =>
|
|
filteredData.some((filtered) =>
|
|
filtered.itemList.some(
|
|
(i) => i.itemId === item.itemId && i.availability
|
|
)
|
|
)
|
|
),
|
|
};
|
|
}
|
|
return cafee;
|
|
});
|
|
localStorage.setItem("cart", JSON.stringify(newLocalStorage));
|
|
|
|
socket.on("transaction_created", () => {
|
|
console.log("transaction created");
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching shop items:", error);
|
|
}
|
|
};
|
|
|
|
if (shopIdentifier !== "") fetchData();
|
|
}, [shopIdentifier]);
|
|
|
|
const rmConnectedGuestSides = async (gueseSideSessionId) => {
|
|
const sessionLeft = await removeConnectedGuestSides(gueseSideSessionId);
|
|
setGuestSides(sessionLeft.guestSideList);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (socket == null) return;
|
|
|
|
if (getLocalStorage("authGuestSide")) {
|
|
socket.emit("checkGuestSideToken", {
|
|
token: getLocalStorage("authGuestSide"),
|
|
});
|
|
} else {
|
|
socket.emit("checkUserToken", {
|
|
token: getLocalStorage("auth"),
|
|
shopId,
|
|
});
|
|
}
|
|
|
|
//for guest
|
|
socket.on("transaction_pending", async (data) => {
|
|
console.log("transaction notification");
|
|
// Call `setModal` with content and parameters
|
|
setModal("transaction_pending", data);
|
|
});
|
|
|
|
socket.on("transaction_confirmed", async (data) => {
|
|
console.log("transaction notification" + data);
|
|
setModal("transaction_confirmed", data);
|
|
});
|
|
|
|
socket.on("transaction_success", async (data) => {
|
|
console.log("transaction notification");
|
|
setModal("transaction_success", data);
|
|
});
|
|
|
|
socket.on("transaction_end", async (data) => {
|
|
console.log("transaction notification");
|
|
setModal("transaction_end", data);
|
|
});
|
|
|
|
socket.on("payment_claimed", async (data) => {
|
|
console.log(data);
|
|
setModal("payment_claimed", data);
|
|
});
|
|
|
|
socket.on("transaction_failed", async (data) => {
|
|
console.log("transaction notification");
|
|
setModal("transaction_failed", data);
|
|
});
|
|
|
|
|
|
const checkNotifications = () => {
|
|
let permission = Notification.permission;
|
|
|
|
// Check current permission
|
|
const searchParams = new URLSearchParams(location.search);
|
|
let searchModal = searchParams.get("modal") || ''; // Get transactionId or set it to empty string
|
|
|
|
if (permission !== "granted" && searchModal == '') {
|
|
setModal("req_notification");
|
|
}
|
|
};
|
|
|
|
socket.on("checkUserTokenRes", async (data) => {
|
|
if (data.status !== 200) {
|
|
removeLocalStorage("auth");
|
|
setDeviceType("guestDevice");
|
|
} else {
|
|
console.log(data)
|
|
setUser(data.data.user);
|
|
if (
|
|
data.data.user.password == "unsetunsetunset" &&
|
|
localStorage.getItem("settings")
|
|
)
|
|
setModal("complete_account");
|
|
if (data.data.user.cafeId == shopId || data.data.isTheOwner) {
|
|
const connectedGuestSides = await getConnectedGuestSides();
|
|
setGuestSides(connectedGuestSides.sessionDatas);
|
|
console.log("getting guest side");
|
|
setDeviceType("clerk");
|
|
|
|
checkNotifications();
|
|
} else {
|
|
setDeviceType("guestDevice");
|
|
}
|
|
if (data.data.user.roleId == 1 && user.userId == shop.ownerId) {
|
|
// shopClerks is can only be obtained by the shop owner
|
|
// so every user that is admin will try to getting shopClerks, even not yet proven that this is their shop
|
|
const shopClerks = await getClerks(shopId);
|
|
if (shopClerks != null) setShopClerks(shopClerks);
|
|
}
|
|
}
|
|
});
|
|
|
|
socket.on("checkGuestSideTokenRes", (data) => {
|
|
if (data.status !== 200) {
|
|
removeLocalStorage("authGuestSide");
|
|
navigate("/guest-side");
|
|
} else {
|
|
setGuestSideOfClerk({
|
|
clerkId: data.sessionData.clerkId,
|
|
clerkUsername: data.sessionData.clerkUsername,
|
|
});
|
|
setDeviceType("guestSide");
|
|
}
|
|
});
|
|
|
|
socket.on("signout-guest-session", () => {
|
|
navigate("/guest-side");
|
|
});
|
|
|
|
socket.on("updateQueue", ({ queue }) => {
|
|
setQueue(queue); // Only set the queue if it's a valid non-empty array
|
|
console.log("Updated Queue:", queue); // Log the valid queue
|
|
|
|
});
|
|
|
|
return () => {
|
|
socket.off("signout-guest-session");
|
|
};
|
|
}, [socket, shopId]);
|
|
|
|
async function checkIfStillViewingOtherTransaction() {
|
|
|
|
console.log("transaction notification");
|
|
console.log(modalContent);
|
|
|
|
let response;
|
|
response = await getTransactionsFromCafe(shopId, 0, true);
|
|
transactionList.current = response;
|
|
console.log(response);
|
|
|
|
// Get current URL's search parameters inside the socket event handler
|
|
const searchParams = new URLSearchParams(location.search);
|
|
let transaction_info = searchParams.get("transactionId") || ''; // Get transactionId or set it to empty string
|
|
console.log(transaction_info); // Log the updated transaction_info
|
|
|
|
// If transaction_info is an empty string, set the modal
|
|
if (transaction_info == '') return false;
|
|
else return true;
|
|
}
|
|
|
|
useEffect(() => {
|
|
// This will ensure that searchParams and transaction_info get updated on each render
|
|
socket.on("transaction_created", async (data) => {
|
|
console.log("transaction notification");
|
|
const isViewingOtherTransaction = await checkIfStillViewingOtherTransaction();
|
|
// If transaction_info is an empty string, set the modal
|
|
if (!isViewingOtherTransaction) {
|
|
setModal("new_transaction", data);
|
|
}
|
|
|
|
// Show browser notification
|
|
let permission = Notification.permission;
|
|
if (permission !== "granted") return;
|
|
navigator.serviceWorker.ready.then((registration) => {
|
|
registration.showNotification("New Transaction", {
|
|
body: `A new transaction was created: ${data.transactionDetails}`,
|
|
icon: "icon.png", // Optional icon
|
|
});
|
|
});
|
|
});
|
|
|
|
socket.on("transaction_canceled", async (data) => {
|
|
console.log("transaction notification");
|
|
|
|
const isViewingOtherTransaction = await checkIfStillViewingOtherTransaction();
|
|
// If transaction_info is an empty string, set the modal
|
|
if (!isViewingOtherTransaction) {
|
|
setModal("new_transaction", data);
|
|
}
|
|
});
|
|
|
|
// Clean up the socket event listener on unmount or when dependencies change
|
|
return () => {
|
|
socket.off("transaction_created");
|
|
socket.off("transaction_canceled");
|
|
};
|
|
}, [socket, shopId, location]); // Ensure location is in the dependencies to respond to changes in the URL
|
|
|
|
useEffect(() => {
|
|
if (user.cafeId != null && user.cafeId !== shopId) {
|
|
// Preserve existing query parameters
|
|
const currentParams = new URLSearchParams(location.search).toString();
|
|
|
|
// Navigate to the new cafeId while keeping existing params
|
|
navigate(`/${user.cafeId}?${currentParams}`, { replace: true });
|
|
}
|
|
|
|
}, [user, shopId]);
|
|
|
|
function handleMoveToTransaction(direction, from) {
|
|
console.log(direction);
|
|
console.log(from);
|
|
|
|
// Find the current index based on the 'from' transactionId
|
|
const currentIndex = transactionList.current.findIndex(item => item.transactionId == from);
|
|
|
|
// Determine the new transactionId based on the direction
|
|
let newTransactionId;
|
|
if (direction === 'next') {
|
|
// If we're not at the last transaction, get the next transactionId
|
|
newTransactionId = currentIndex < transactionList.current.length - 1
|
|
? transactionList.current[currentIndex + 1].transactionId
|
|
: from; // If already at the end, stay on the current transactionId
|
|
} else if (direction === 'previous') {
|
|
// If we're not at the first transaction, get the previous transactionId
|
|
newTransactionId = currentIndex > 0
|
|
? transactionList.current[currentIndex - 1].transactionId
|
|
: from; // If already at the beginning, stay on the current transactionId
|
|
}
|
|
|
|
// Log the new transactionId
|
|
console.log('New Transaction ID:', newTransactionId);
|
|
|
|
// Update the URL with the new transactionId using navigate
|
|
navigate(`?transactionId=${newTransactionId}`, { replace: true });
|
|
|
|
// Optionally, update state or perform further actions based on the new transactionId
|
|
// Example:
|
|
// setModalContent({ cafeId: shopId, transactionId: newTransactionId });
|
|
}
|
|
|
|
|
|
const handleModalFromURL = () => {
|
|
const queryParams = new URLSearchParams(location.search);
|
|
const modal = queryParams.get("modal");
|
|
if (modal) setModal(modal);
|
|
};
|
|
|
|
useEffect(() => {
|
|
handleModalFromURL();
|
|
}, [shopId]);
|
|
|
|
useEffect(() => {
|
|
console.log(shopId + table?.tableCode);
|
|
}, [navigate]);
|
|
|
|
// Function to open the modal
|
|
const setModal = (content, params = {}, onCloseFunction) => {
|
|
const queryParams = new URLSearchParams(location.search);
|
|
|
|
// Update the modal and any additional params
|
|
queryParams.set("modal", content);
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
queryParams.set(key, value);
|
|
});
|
|
|
|
// Update URL with new parameters
|
|
navigate(`?${queryParams.toString()}`, { replace: true });
|
|
|
|
// Prevent scrolling when modal is open
|
|
document.body.style.overflow = "hidden";
|
|
|
|
setIsModalOpen(true);
|
|
setModalContent(content)
|
|
console.log(onCloseFunction)
|
|
|
|
if (onCloseFunction) {
|
|
setOnModalCloseFunction(() => onCloseFunction); // Store the close function
|
|
} else {
|
|
setOnModalCloseFunction(null);
|
|
}
|
|
};
|
|
|
|
const closeModal = (closeTheseContent = []) => {
|
|
if (
|
|
Array.isArray(closeTheseContent) &&
|
|
(closeTheseContent.length === 0 || closeTheseContent.includes(modalContent))
|
|
) {
|
|
setIsModalOpen(false);
|
|
setModalContent(null);
|
|
document.body.style.overflow = "auto";
|
|
|
|
const queryParams = new URLSearchParams(location.search);
|
|
|
|
// Clear all query parameters
|
|
queryParams.keys() && [...queryParams.keys()].forEach(key => {
|
|
queryParams.delete(key);
|
|
});
|
|
|
|
// Update the URL without any query parameters
|
|
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(() => {
|
|
const startPermissionPolling = async () => {
|
|
const checkInterval = 5000; // Check every 5 seconds
|
|
|
|
const intervalId = setInterval(async () => {
|
|
const permission = Notification.permission;
|
|
|
|
if (permission === "granted") {
|
|
await resetNotificationSubscription();
|
|
clearInterval(intervalId); // Stop checking once subscribed
|
|
} else if (permission === "denied") {
|
|
console.error("Notification permission denied.");
|
|
}
|
|
}, checkInterval);
|
|
};
|
|
|
|
startPermissionPolling();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if ("serviceWorker" in navigator) {
|
|
navigator.serviceWorker.register("/service-worker.js")
|
|
.then(registration => {
|
|
console.log("Service Worker registered with scope:", registration.scope);
|
|
})
|
|
.catch(error => {
|
|
console.error("Service Worker registration failed:", error);
|
|
});
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<div className="App">
|
|
<header className="App-header" id="header">
|
|
<Routes>
|
|
<Route
|
|
path="/"
|
|
element={
|
|
<Dashboard user={user} socket={socket} setModal={setModal} />
|
|
}
|
|
/>
|
|
<Route
|
|
path="/scan"
|
|
element={
|
|
<>
|
|
<ScanMeja
|
|
sendParam={handleSetParam}
|
|
shopName={shop.name}
|
|
shopOwnerId={shop.ownerId}
|
|
shopItems={shopItems}
|
|
shopClerks={shopClerks}
|
|
socket={socket}
|
|
user={user}
|
|
guestSides={guestSides}
|
|
guestSideOfClerk={guestSideOfClerk}
|
|
removeConnectedGuestSides={rmConnectedGuestSides}
|
|
setModal={setModal} // Pass the function to open modal
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/:shopIdentifier/:tableCode?"
|
|
element={
|
|
<>
|
|
<CafePage
|
|
shopId={shopId}
|
|
table={table}
|
|
sendParam={handleSetParam}
|
|
welcomePageConfig={shop.welcomePageConfig || false}
|
|
shopName={shop.name}
|
|
shopOwnerId={shop.ownerId}
|
|
shopItems={shopItems}
|
|
setShopItems={setShopItems}
|
|
shopClerks={shopClerks}
|
|
socket={socket}
|
|
user={user}
|
|
guestSides={guestSides}
|
|
guestSideOfClerk={guestSideOfClerk}
|
|
removeConnectedGuestSides={rmConnectedGuestSides}
|
|
setModal={setModal} // Pass the function to open modal
|
|
loading={shop.name == null}
|
|
queue={queue}
|
|
/>
|
|
<Footer
|
|
showTable={true}
|
|
shopId={shopIdentifier}
|
|
table={table}
|
|
cartItemsLength={totalItemsCount}
|
|
selectedPage={0}
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/:shopIdentifier/:tableCode?/search"
|
|
element={
|
|
<>
|
|
<SearchResult
|
|
cafeId={shopId}
|
|
sendParam={handleSetParam}
|
|
user={user}
|
|
shopItems={shopItems}
|
|
guestSides={guestSides}
|
|
guestSideOfClerk={guestSideOfClerk}
|
|
removeConnectedGuestSides={rmConnectedGuestSides}
|
|
setModal={setModal} // Pass the function to open modal
|
|
/>
|
|
<Footer
|
|
shopId={shopIdentifier}
|
|
table={table}
|
|
cartItemsLength={totalItemsCount}
|
|
selectedPage={1}
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/:shopIdentifier/:tableCode?/cart"
|
|
element={
|
|
<>
|
|
<Cart
|
|
shopId={shopId}
|
|
table={table}
|
|
sendParam={handleSetParam}
|
|
socket={socket}
|
|
totalItemsCount={totalItemsCount}
|
|
deviceType={deviceType}
|
|
/>
|
|
<Footer
|
|
shopId={shopIdentifier}
|
|
table={table}
|
|
cartItemsLength={totalItemsCount}
|
|
selectedPage={2}
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/:shopIdentifier/:tableCode?/invoice"
|
|
element={
|
|
<>
|
|
<Invoice
|
|
shopId={shopId}
|
|
table={table}
|
|
sendParam={handleSetParam}
|
|
socket={socket}
|
|
deviceType={deviceType}
|
|
/>
|
|
<Footer
|
|
shopId={shopIdentifier}
|
|
table={table}
|
|
cartItemsLength={totalItemsCount}
|
|
selectedPage={2}
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/:shopIdentifier/:tableCode?/transactions"
|
|
element={
|
|
<>
|
|
<Transactions
|
|
shopId={shopId}
|
|
sendParam={handleSetParam}
|
|
deviceType={deviceType}
|
|
/>
|
|
<Footer
|
|
shopId={shopIdentifier}
|
|
table={table}
|
|
cartItemsLength={totalItemsCount}
|
|
selectedPage={3}
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/:shopIdentifier/guest-side-login"
|
|
element={<GuestSideLogin shopId={shopId} socket={socket} />}
|
|
/>
|
|
<Route path="/guest-side" element={<GuestSide socket={socket} />} />
|
|
</Routes>
|
|
</header>
|
|
<Modal
|
|
user={user}
|
|
shop={shop}
|
|
isOpen={isModalOpen}
|
|
modalContent={modalContent}
|
|
handleMoveToTransaction={handleMoveToTransaction}
|
|
welcomePageConfig={shop.welcomePageConfig}
|
|
onClose={closeModal}
|
|
setModal={setModal}
|
|
onModalCloseFunction={onModalCloseFunction}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const AppWrapper = () => (
|
|
<Router>
|
|
<App />
|
|
</Router>
|
|
);
|
|
|
|
export default AppWrapper;
|