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 });
}
};
// 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 askNotificationPermission = async () => {
let permission = Notification.permission;
const startPermissionPolling = async () => {
const checkInterval = 5000; // Check every 5 seconds
// Check current permission
if (permission === "default") {
setModal("req_notification");
const intervalId = setInterval(async () => {
const permission = Notification.permission;
// Request permission and wait for the result
permission = await Notification.requestPermission();
}
// If permission is already granted, reset subscriptions
if (permission === "granted") {
await resetNotificationSubscription();
closeModal(["req_notification", "denied_notification"]);
} else if (permission === "denied") {
setModal("blocked_notification");
console.error("Notification permission denied.");
}
// 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"]);
if (permission === "granted") {
await resetNotificationSubscription();
break;
} else if (permissionn === "denied") {
setModal("blocked_notification");
clearInterval(intervalId); // Stop checking once subscribed
} else if (permission === "denied") {
console.error("Notification permission denied.");
break;
}
}
}, checkInterval);
};
const handleLoad = async () => {
const ses = sessionStorage.getItem("notifAsk");
if (!ses && user != null && (user.roleId < 3 || user.roleId > 2)) {
await askNotificationPermission();
sessionStorage.setItem("notifAsk", true);
}
};
handleLoad();
startPermissionPolling();
}, []);
useEffect(() => {
if ("serviceWorker" in navigator) {
window.addEventListener("load", handleLoad);
// Cleanup the event listener on component unmount
return () => {
window.removeEventListener("load", handleLoad);
};
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);
});
}
}, [user]);
}, []);
return (
<div className="App">
@@ -524,6 +535,7 @@ function App() {
isOpen={isModalOpen}
modalContent={modalContent}
onClose={closeModal}
setModal={setModal}
/>
</div>
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,8 @@ import MaterialMutationsPage from "../pages/MaterialMutationsPage.js";
import Reports from "../pages/Reports.js";
import NotificationBlocked from "../pages/NotificationBlocked.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;
// Function to handle clicks on the overlay
@@ -33,9 +34,6 @@ const Modal = ({ shop, isOpen, onClose, modalContent }) => {
return (
<div onClick={handleOverlayClick} className={styles.modalOverlay}>
<div className={styles.modalContent} onClick={handleContentClick}>
<button onClick={() => onClose()} className={styles.closeButton}>
&times;
</button>
{modalContent === "req_notification" && <NotificationBlocked />}
{modalContent === "blocked_notification" && <NotificationBlocked />}
{modalContent === "create_clerk" && <CreateClerk shopId={shop.cafeId} />}
@@ -53,7 +51,11 @@ const Modal = ({ shop, isOpen, onClose, modalContent }) => {
{modalContent === "payment_claimed" && (
<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_failed" && <Transaction_failed />}
{modalContent === "payment_option" && (

View File

@@ -29,6 +29,7 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
const [backgroundImage, setBackgroundImage] = useState("");
const [canvaz, setCanvaz] = useState('');
const [videoSrc, setVideoSrc] = useState('');
const videoRef = useRef(null);
@@ -149,39 +150,41 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
};
}, [socket]);
// useEffect for setting up the socket listener
useEffect(() => {
const handleUpdateCanvas = (response) => {
if (response) {
// useEffect for setting up the socket listener
useEffect(() => {
const handleUpdateCanvas = (response) => {
if (response && response !== canvaz) {
console.log(response);
console.log(canvaz);
setCanvaz(response);
fetch(response)
.then((response) => response.blob())
.then((blob) => {
const blobUrl = URL.createObjectURL(blob);
setVideoSrc(blobUrl);
fetch(response)
.then((response) => response.blob())
.then((blob) => {
const blobUrl = URL.createObjectURL(blob);
setVideoSrc(blobUrl);
if (videoRef.current) {
videoRef.current.load(); // Reload the video element
}
})
.catch((error) => console.error('Error loading video:', error));
if (videoRef.current) {
videoRef.current.load(); // Reload the video element
}
})
.catch((error) => console.error('Error loading video:', error));
} else if (!response) {
// Clear the video source if response is empty
setVideoSrc('');
if (videoRef.current) {
videoRef.current.load(); // Reload the video element
}
else{
setVideoSrc('');
if (videoRef.current) {
videoRef.current.load(); // Reload the video element
}
}
};
}
};
// Listen for the "updateCanvas" event
socket.on("updateCanvas", handleUpdateCanvas);
// Listen for the "updateCanvas" event
socket.on("updateCanvas", handleUpdateCanvas);
// Clean up the socket listener when the component is unmounted
return () => {
socket.off("updateCanvas", handleUpdateCanvas);
};
}, [socket]);
// Clean up the socket listener when the component is unmounted
return () => {
socket.off("updateCanvas", handleUpdateCanvas);
};
}, [socket, canvaz]);
useEffect(() => {
// 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");
if (token) {
try {
@@ -266,7 +266,6 @@ export const createClerks = async (shopId, email, username, password) => {
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
email: email,
username: username,
password: password,
}),

View File

@@ -20,7 +20,7 @@ import Header from "../components/Header";
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 WelcomePage from "./WelcomePage.js";
@@ -94,8 +94,29 @@ function CafePage({
// Navigate to the new cafeId while keeping existing params
navigate(`/${user.cafeId}?${currentParams}`, { replace: true });
}
}, [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(() => {
if (token) {
updateLocalStorage("auth", token);
@@ -172,12 +193,12 @@ function CafePage({
setIsEditMode={(e) => setIsEditMode(e)}
isEditMode={isEditMode}
/>
<MusicPlayer
socket={socket}
shopId={shopId}
user={user}
isSpotifyNeedLogin={isSpotifyNeedLogin}
/>
<MusicPlayer
socket={socket}
shopId={shopId}
user={user}
isSpotifyNeedLogin={isSpotifyNeedLogin}
/>
<ItemTypeLister
user={user}
shopOwnerId={shopOwnerId}

View File

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

View File

@@ -108,7 +108,7 @@ const Dashboard = ({ user, setModal }) => {
className={styles.rectangle}
onClick={() => setIsCreating(true)}
>
Create Admin
Create Client
</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 {
const fetchedTransaction = await getTransaction(transactionId);
setTransaction(fetchedTransaction);
console.log(transaction);
console.log(fetchedTransaction); // Log the fetched transaction
} catch (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]);
useEffect(() => {
@@ -189,14 +201,17 @@ export default function Transactions({
price={
"Rp" + calculateTotalPrice(transaction.DetailedTransactions)
}
disabled={isPaymentLoading}
disabled={transaction.payment_type == 'cash' || isPaymentLoading}
isPaymentLoading={isPaymentLoading}
handleClick={() => handleConfirm(transaction.transactionId)}
Open={() => setIsPaymentOpen(true)}
isPaymentOpen={isPaymentOpen}
>
{isPaymentLoading ? (
{transaction.payment_type == 'cash' || isPaymentLoading ? (
<>
{transaction.payment_type == 'cash' && <p>tunggu konfirmasi</p>}
<ColorRing height="50" width="50" color="white" />
</>
) : isPaymentOpen ? (
"Claim has paid" // Display "Confirm has paid" if the transaction is confirmed (1)
) : (

View File

@@ -1,29 +1,57 @@
import React from "react";
import { ColorRing } from "react-loader-spinner";
import styles from "./Transactions.module.css";
import { requestNotificationPermission } from '../services/notificationService'; // Import the notification service
export default function Transaction_pending() {
const containerStyle = {
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
height: "100%", // This makes the container stretch to the bottom of the viewport
backgroundColor: "#000", // Optional: Set a background color if you want to see the color ring clearly
export default function Transaction_pending({ setModal }) {
// const containerStyle = {
// display: "flex",
// justifyContent: "center",
// alignItems: "center",
// width: "100%",
// height: "100%",
// 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 (
<div className={styles.Transactions}>
<div className={containerStyle}>
<div style={{ marginTop: "30px", textAlign: "center" }}>
<h2>transaction success</h2>
<h2>Transaction Success</h2>
<img
className={styles.expression}
src="https://i.imgur.com/sgvMI02.pngs"
src="https://i.imgur.com/sgvMI02.png"
alt="Success"
/>
<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>
);
}

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;
};