diff --git a/package-lock.json b/package-lock.json index 0da4796..4f419e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "react-router-dom": "^6.24.0", "react-scripts": "5.0.1", "react-switch": "^7.0.0", + "react-youtube": "^10.1.0", "smooth-scroll-into-view-if-needed": "^2.0.2", "socket.io-client": "^4.7.5", "styled-components": "^6.1.11", @@ -13339,6 +13340,11 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -16220,6 +16226,22 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-youtube": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-10.1.0.tgz", + "integrity": "sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==", + "dependencies": { + "fast-deep-equal": "3.1.3", + "prop-types": "15.8.1", + "youtube-player": "5.5.2" + }, + "engines": { + "node": ">= 14.x" + }, + "peerDependencies": { + "react": ">=0.14.1" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17084,6 +17106,11 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/sister": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz", + "integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA==" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -19567,6 +19594,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/youtube-player": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz", + "integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==", + "dependencies": { + "debug": "^2.6.6", + "load-script": "^1.0.0", + "sister": "^3.0.0" + } + }, + "node_modules/youtube-player/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/youtube-player/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } } diff --git a/package.json b/package.json index 0cc9f49..8eea11c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react-router-dom": "^6.24.0", "react-scripts": "5.0.1", "react-switch": "^7.0.0", + "react-youtube": "^10.1.0", "smooth-scroll-into-view-if-needed": "^2.0.2", "socket.io-client": "^4.7.5", "styled-components": "^6.1.11", diff --git a/src/App.js b/src/App.js index 5fdd428..86db5de 100644 --- a/src/App.js +++ b/src/App.js @@ -208,6 +208,15 @@ function App() { }); }); + const checkNotifications = () => { + let permission = Notification.permission; + + // Check current permission + if (permission !== "granted") { + setModal("req_notification"); + } + }; + socket.on("checkUserTokenRes", async (data) => { if (data.status !== 200) { removeLocalStorage("auth"); @@ -226,7 +235,7 @@ function App() { console.log("getting guest side"); setDeviceType("clerk"); - // checkNotifications(data.data.user.userId); + checkNotifications(); } else { setDeviceType("guestDevice"); } @@ -315,6 +324,7 @@ function App() { navigate({ search: queryParams.toString() }, { replace: true }); } }; + // useEffect(() => { // const askNotificationPermission = async () => { // let permission = Notification.permission; diff --git a/src/components/Modal.js b/src/components/Modal.js index 08dfd79..f5d0eda 100644 --- a/src/components/Modal.js +++ b/src/components/Modal.js @@ -14,6 +14,7 @@ import Payment_claimed from "../pages/Payment_claimed"; import MaterialList from "../pages/MaterialList.js"; import MaterialMutationsPage from "../pages/MaterialMutationsPage.js"; import Reports from "../pages/Reports.js"; +import NotificationRequest from "../pages/NotificationRequest.js"; import NotificationBlocked from "../pages/NotificationBlocked.js"; import WelcomePageEditor from "../pages/WelcomePageEditor.js"; import GuidePage from "../pages/GuidePage"; @@ -34,7 +35,7 @@ const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => { return (
- {modalContent === "req_notification" && } + {modalContent === "req_notification" && } {modalContent === "blocked_notification" && } {modalContent === "create_clerk" && } {modalContent === "edit_tables" && } diff --git a/src/components/Modal.module.css b/src/components/Modal.module.css index a4c93dd..c903fc3 100644 --- a/src/components/Modal.module.css +++ b/src/components/Modal.module.css @@ -2,7 +2,7 @@ position: fixed; top: 0; left: 0; - right: 0; + right: -1px; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; diff --git a/src/components/TrackPlayer.js b/src/components/TrackPlayer.js new file mode 100644 index 0000000..5e7a436 --- /dev/null +++ b/src/components/TrackPlayer.js @@ -0,0 +1,108 @@ +import React, { useState, useEffect, useRef } from 'react'; +import YouTube from 'react-youtube'; + +export function TrackPlayer({ next }) { + // State to store the progress in milliseconds, video duration, and the next video ID + const [progress, setProgress] = useState(0); + const [duration, setDuration] = useState(0); + const [currentTrack, setCurrentTrack] = useState(null); // Initial video ID + const [nextTrack, setNextTrack] = useState(null); // Initial video ID + const [isNearEnd, setIsNearEnd] = useState(false); // Flag for 20 seconds left + const playerRef = useRef(null); + + useEffect(() => { + if (next == null) return; + if (currentTrack == null) setCurrentTrack(next); + setNextTrack(next); + }, [next]); + + const handlePlayerStateChange = (event) => { + if (event.data === window.YT.PlayerState.PLAYING) { + + // Start tracking progress once the video starts playing + const interval = setInterval(() => { + if (playerRef.current) { + const currentTime = playerRef.current.getCurrentTime(); // Get current time in seconds + setProgress(currentTime * 1000); // Convert to milliseconds + // Check if the video is 20 seconds from ending + if (currentTime >= duration / 1000 - 20 && !isNearEnd) { + setIsNearEnd(true); + } else if (currentTime < duration / 1000 - 20 && isNearEnd) { + setIsNearEnd(false); + } + } + }, 100); // Update every 100 ms + + // Clean up when the video is paused or finished + event.target.addEventListener('onStateChange', (e) => { + if (e.data === window.YT.PlayerState.PAUSED || e.data === window.YT.PlayerState.ENDED) { + clearInterval(interval); + + // When the video ends, set the next track + if (e.data === window.YT.PlayerState.ENDED) { + // Logic to set the next video ID (for now, just updating to another static video) + setCurrentTrack(nextTrack); // Replace 'newVideoId' with the ID of the next video you want + } + } + }); + } + }; + + const handlePlayerReady = (event) => { + playerRef.current = event.target; + const durationInSeconds = playerRef.current.getDuration(); // Get video duration in seconds + setDuration(durationInSeconds * 1000); // Set the duration in milliseconds + + // Set the video quality to the lowest available (typically 360p or smaller) + playerRef.current.setPlaybackQuality('small'); + }; + + useEffect(() => { + // Make sure to load the YouTube iframe API script when the component mounts + const script = document.createElement('script'); + script.src = 'https://www.youtube.com/iframe_api'; + document.body.appendChild(script); + + return () => { + // Cleanup if needed (for example, removing the script) + document.body.removeChild(script); + }; + }, []); + + useEffect(() => { + // When the currentTrack changes, reset progress and duration + setProgress(0); + setDuration(0); + setIsNearEnd(false); + }, [currentTrack]); + + return ( +
+ {currentTrack != null && ( +
+ +
+ )} +
Progress: {progress} ms
+
Video Duration: {duration} ms
+
+ {isNearEnd &&
Video is near the end (20 seconds left)
} +
+
+ ); +} diff --git a/src/pages/Dashboard.js b/src/pages/Dashboard.js index eca5a20..dea637e 100644 --- a/src/pages/Dashboard.js +++ b/src/pages/Dashboard.js @@ -59,8 +59,8 @@ const Dashboard = ({ user, setModal }) => { if (user.roleId < 1) { // Create admin functionality createCafeOwner(newItem.email, newItem.username, newItem.password) - .then(() => { - setItems([...items, { name: newItem.username }]); + .then((newitem) => { + setItems([...items, { userId: newitem.userId, name: newitem.username }]); setIsCreating(false); setNewItem({ name: "", type: "" }); }) @@ -70,8 +70,8 @@ const Dashboard = ({ user, setModal }) => { } else { // Create cafe functionality createCafe(newItem.name) - .then(() => { - setItems([...items, { name: newItem.name }]); + .then((newitem) => { + setItems([...items, { cafeId: newitem.cafeId, name: newitem.name }]); setIsCreating(false); setNewItem({ name: "", type: "" }); }) @@ -126,8 +126,7 @@ const Dashboard = ({ user, setModal }) => { className={styles.rectangle} >

{item.name || item.username}

- -

{item.report.totalIncome}

{item.report.totalIncome}

+

{item.report?.totalIncome}

))} {user && user.roleId < 1 ? ( diff --git a/src/pages/NotificationBlocked.js b/src/pages/NotificationBlocked.js index fbfd9df..6740ea2 100644 --- a/src/pages/NotificationBlocked.js +++ b/src/pages/NotificationBlocked.js @@ -3,27 +3,42 @@ import React from "react"; const NotificationBlocked = () => { return (
-

Heads Up! Notifications Are Off

+

Notifikasi Terblokir

- It looks like you’ve got notifications turned off. Turning them on will - make sure you get important updates, like new orders or alerts, right on - your device. + Sepertinya notifikasi untuk situs ini tidak aktif. Aktifkan notifikasi supaya kamu tetap dapat info pesanan, meski sedang buka aplikasi lain. +

+

+ Berikut cara mengaktifkannya:

-

Here’s how to turn them on:

    -
  1. Open Chrome and go to our café's website.
  2. -
  3. Tap the menu (three dots) in the top-right corner.
  4. - Go to Settings > Site settings{" "} - > Notifications. + Klik ikon{" "} + + + + + + + + + {" "} + di pojok kiri atas
  5. - Find our café in the list and set it to Allow. + Pilih Izin > Notifikasi > Izinkan Notifikasi.

- Once you’ve turned on notifications, you’ll start getting updates - instantly. Need a hand? Just ask! + Setelah itu, kamu bisa terus dapet informasi pesanan meski gak lagi buka situs ini.

); @@ -44,11 +59,7 @@ const styles = { color: "#e74c3c", }, message: { - marginBottom: "20px", - }, - instructionsHeader: { - marginTop: "20px", - fontWeight: "bold", + marginBottom: "15px", }, instructions: { listStyleType: "decimal", @@ -59,6 +70,13 @@ const styles = { marginTop: "20px", fontStyle: "italic", }, + icon: { + display: "inline-block", + verticalAlign: "middle", + width: "20px", + height: "20px", + marginBottom: '6px' + }, }; export default NotificationBlocked; diff --git a/src/pages/NotificationRequest.js b/src/pages/NotificationRequest.js new file mode 100644 index 0000000..2d78e69 --- /dev/null +++ b/src/pages/NotificationRequest.js @@ -0,0 +1,57 @@ +import React from "react"; +import styles from "./Transactions.module.css"; +import { requestNotificationPermission } from '../services/notificationService'; // Import the notification service + +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 { + console.error("Notification permission denied."); + setModal('blocked_notification'); // Show modal for blocked notifications + } + }; + + return ( +
+
+

Aktifkan Notifikasi

+ Success +

+ Sepertinya notifikasi untuk situs ini tidak aktif. Aktifkan notifikasi supaya kamu tetap dapat info pesanan, meski sedang buka aplikasi lain. +

+ +
+
+ ); +}