Files
AnythingYouWant/src/App.js
insvrgent e2b0ef1046 ok
2025-02-06 02:38:50 +07:00

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;