ok
This commit is contained in:
@@ -2,19 +2,22 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/kedaimaster150.png" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta name="description" content="Solusi berbasis web untuk memudahkan pengelolaan kedai, dengan fitur yang mempermudah pemilik, kasir, dan tamu berinteraksi." />
|
||||||
name="description"
|
|
||||||
content="Web site created using create-react-app"
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/kedaimaster150.png" />
|
||||||
/>
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
|
||||||
|
<meta property="og:image" content="%PUBLIC_URL%/kedaimaster300.png" />
|
||||||
|
<meta name="twitter:image" content="%PUBLIC_URL%/kedaimaster300.png" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
|||||||
BIN
public/kedai.png
Normal file
BIN
public/kedai.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
BIN
public/kedaimaster150.png
Normal file
BIN
public/kedaimaster150.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
public/kedaimaster300.png
Normal file
BIN
public/kedaimaster300.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
public/laporan.png
Normal file
BIN
public/laporan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
10
src/App.js
10
src/App.js
@@ -14,7 +14,6 @@ import API_BASE_URL from "./config.js";
|
|||||||
|
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
import ScanMeja from "./pages/ScanMeja";
|
import ScanMeja from "./pages/ScanMeja";
|
||||||
import LoginPage from "./pages/LoginPage";
|
|
||||||
import CafePage from "./pages/CafePage";
|
import CafePage from "./pages/CafePage";
|
||||||
import SearchResult from "./pages/SearchResult";
|
import SearchResult from "./pages/SearchResult";
|
||||||
import Cart from "./pages/Cart";
|
import Cart from "./pages/Cart";
|
||||||
@@ -58,6 +57,7 @@ function App() {
|
|||||||
const [shopItems, setShopItems] = useState([]);
|
const [shopItems, setShopItems] = useState([]);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [modalContent, setModalContent] = useState(null);
|
const [modalContent, setModalContent] = useState(null);
|
||||||
|
const [queue, setQueue] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const calculateTotalsFromLocalStorage = () => {
|
const calculateTotalsFromLocalStorage = () => {
|
||||||
@@ -265,6 +265,12 @@ function App() {
|
|||||||
navigate("/guest-side");
|
navigate("/guest-side");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("updateQueue", (response) => {
|
||||||
|
setQueue(response); // Only set the queue if it's a valid non-empty array
|
||||||
|
console.log("Updated Queue:", response); // Log the valid queue
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("signout-guest-session");
|
socket.off("signout-guest-session");
|
||||||
};
|
};
|
||||||
@@ -403,7 +409,6 @@ function App() {
|
|||||||
<Dashboard user={user} socket={socket} setModal={setModal} />
|
<Dashboard user={user} socket={socket} setModal={setModal} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
|
||||||
<Route
|
<Route
|
||||||
path="/scan"
|
path="/scan"
|
||||||
element={
|
element={
|
||||||
@@ -444,6 +449,7 @@ function App() {
|
|||||||
removeConnectedGuestSides={rmConnectedGuestSides}
|
removeConnectedGuestSides={rmConnectedGuestSides}
|
||||||
setModal={setModal} // Pass the function to open modal
|
setModal={setModal} // Pass the function to open modal
|
||||||
loading={shop.name==null}
|
loading={shop.name==null}
|
||||||
|
queue={queue}
|
||||||
/>
|
/>
|
||||||
<Footer
|
<Footer
|
||||||
showTable={true}
|
showTable={true}
|
||||||
|
|||||||
106
src/Apppp.js
Normal file
106
src/Apppp.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {TrackPlayer} from './components/TrackPlayer';
|
||||||
|
|
||||||
|
const Modal = ({ isOpen, onClose, title, message }) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={styles.modalOverlay}>
|
||||||
|
<div style={styles.modal}>
|
||||||
|
<h2>{title}</h2>
|
||||||
|
<p>{message}</p>
|
||||||
|
<button onClick={onClose}>Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
modalOverlay: {
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
background: 'white',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '5px',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [modal, setModal] = useState({ isOpen: false, title: '', message: '' });
|
||||||
|
const [playerSocket, setPlayerSocket] = useState(null);
|
||||||
|
const [queue, setQueue] = useState(null);
|
||||||
|
const [next, setNext] = useState(null);
|
||||||
|
const [ok, setOk] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const shopId = window.location.pathname.split('/')[1]; // Get shopId from the URL
|
||||||
|
const userId = localStorage.getItem('userId');
|
||||||
|
|
||||||
|
// Connect to Socket.IO if userId is present
|
||||||
|
// if (userId) {
|
||||||
|
// connectSocket(shopId, 1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const socket = getSocket();
|
||||||
|
|
||||||
|
// Modal opening function
|
||||||
|
const openModal = (title, message) => {
|
||||||
|
setModal({ isOpen: true, title, message });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Socket event listeners
|
||||||
|
// if (socket) {
|
||||||
|
// socket.on('pleaseSelect', () => openModal('Select a Track', 'Please select a track to play.'));
|
||||||
|
// socket.on('pleasePlay', () => openModal('Play Track', 'Please play the selected track.'));
|
||||||
|
// socket.on('playSuccess', () => openModal('Success', 'The track is now playing.'));
|
||||||
|
// socket.on('updateMusicInfo', ({ playerSocket, queue }) => {
|
||||||
|
// setPlayerSocket(playerSocket);
|
||||||
|
// setQueue(queue);
|
||||||
|
// });
|
||||||
|
// socket.on('setNext', (nextTrack) => setNext(nextTrack));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Cleanup on component unmount
|
||||||
|
return () => {
|
||||||
|
// if (socket) {
|
||||||
|
// socket.off('pleaseSelect');
|
||||||
|
// socket.off('pleasePlay');
|
||||||
|
// socket.off('playSuccess');
|
||||||
|
// socket.off('updateMusicInfo');
|
||||||
|
// socket.off('setNext');
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setModal({ ...modal, isOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
isOpen={modal.isOpen}
|
||||||
|
onClose={closeModal}
|
||||||
|
title={modal.title}
|
||||||
|
message={modal.message}
|
||||||
|
/>
|
||||||
|
{/* {playerSocket === getSocket()?.id && queue && ( */}
|
||||||
|
{ok &&
|
||||||
|
<TrackPlayer next={next} queue={queue} />
|
||||||
|
}
|
||||||
|
<button onClick={()=>setOk(true)}>ok</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -20,7 +20,7 @@ const Title = styled.h2`
|
|||||||
font-family: "Poppins", sans-serif;
|
font-family: "Poppins", sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 6vw;
|
font-size:${(props) => (props.HeaderSize)};
|
||||||
color: rgba(88, 55, 50, 1);
|
color: rgba(88, 55, 50, 1);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -214,6 +214,7 @@ const Child = styled.div`
|
|||||||
|
|
||||||
const Header = ({
|
const Header = ({
|
||||||
HeaderText,
|
HeaderText,
|
||||||
|
HeaderSize='6vw',
|
||||||
shopId,
|
shopId,
|
||||||
shopName,
|
shopName,
|
||||||
shopImage,
|
shopImage,
|
||||||
@@ -300,7 +301,7 @@ const Header = ({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<HeaderBar>
|
<HeaderBar>
|
||||||
<Title>
|
<Title HeaderSize={HeaderSize}>
|
||||||
{shopName == null
|
{shopName == null
|
||||||
? HeaderText == null
|
? HeaderText == null
|
||||||
? "kedaimaster"
|
? "kedaimaster"
|
||||||
@@ -309,9 +310,9 @@ const Header = ({
|
|||||||
</Title>
|
</Title>
|
||||||
<div style={{ visibility: showProfile ? "visible" : "hidden" }}>
|
<div style={{ visibility: showProfile ? "visible" : "hidden" }}>
|
||||||
<ProfileImage
|
<ProfileImage
|
||||||
src={shopImage || "https://static-00.iconduck.com/assets.00/profile-major-icon-1024x1024-9rtgyx30.png"}
|
src={user.username == undefined? shopImage : "https://static-00.iconduck.com/assets.00/profile-major-icon-1024x1024-9rtgyx30.png"}
|
||||||
alt="Profile"
|
alt="Profile"
|
||||||
onClick={handleImageClick}
|
onClick={user.username !== undefined?handleImageClick: null}
|
||||||
animate={showRectangle && animate}
|
animate={showRectangle && animate}
|
||||||
/>
|
/>
|
||||||
<ProfileName animate={showRectangle && animate}>
|
<ProfileName animate={showRectangle && animate}>
|
||||||
@@ -325,9 +326,6 @@ const Header = ({
|
|||||||
this is the guest side of {guestSideOfClerk.clerkUsername}
|
this is the guest side of {guestSideOfClerk.clerkUsername}
|
||||||
</Child>
|
</Child>
|
||||||
)}
|
)}
|
||||||
{user.username === undefined && !guestSideOfClerk && (
|
|
||||||
<Child onClick={goToLogin}>Click to login</Child>
|
|
||||||
)}
|
|
||||||
{user.username !== undefined && (
|
{user.username !== undefined && (
|
||||||
<Child onClick={() => setModal("edit_account")}>
|
<Child onClick={() => setModal("edit_account")}>
|
||||||
Ubah profil
|
Ubah profil
|
||||||
@@ -340,22 +338,17 @@ const Header = ({
|
|||||||
user.username !== undefined &&
|
user.username !== undefined &&
|
||||||
user.roleId === 1 && (
|
user.roleId === 1 && (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
{/* <Child onClick={() => setModal("update_stock")}>
|
|
||||||
update stock
|
|
||||||
</Child> */}
|
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
{shopName}
|
{shopName}
|
||||||
|
|
||||||
<div class="toggle-switch">
|
<Child>
|
||||||
<label class="toggle-switch-label" for="toggleSwitch">
|
|
||||||
Mode edit
|
Mode edit
|
||||||
</label>
|
|
||||||
<Switch
|
<Switch
|
||||||
|
borderRadius={0}
|
||||||
checked={isEditMode}
|
checked={isEditMode}
|
||||||
onChange={() => setIsEditMode(!isEditMode)}
|
onChange={() => setIsEditMode(!isEditMode)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Child>
|
||||||
|
|
||||||
<Child onClick={() => setModal("welcome_config")}>
|
<Child onClick={() => setModal("welcome_config")}>
|
||||||
Halaman sambutan
|
Halaman sambutan
|
||||||
@@ -401,15 +394,14 @@ const Header = ({
|
|||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
{shopName}
|
{shopName}
|
||||||
|
|
||||||
<div class="toggle-switch">
|
<Child>
|
||||||
<label class="toggle-switch-label" for="toggleSwitch">
|
Mode edit
|
||||||
Mode edit
|
|
||||||
</label>
|
|
||||||
<Switch
|
<Switch
|
||||||
|
borderRadius={0}
|
||||||
checked={isEditMode}
|
checked={isEditMode}
|
||||||
onChange={() => setIsEditMode(!isEditMode)}
|
onChange={() => setIsEditMode(!isEditMode)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Child>
|
||||||
<Child onClick={() => setModal("add_material")}>
|
<Child onClick={() => setModal("add_material")}>
|
||||||
stok
|
stok
|
||||||
</Child>
|
</Child>
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ import NotificationRequest from "../pages/NotificationRequest.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";
|
||||||
import GuidePage from "../pages/GuidePage";
|
import GuidePage from "../pages/GuidePage";
|
||||||
|
import Join from "../pages/Join";
|
||||||
|
import Login from "../pages/Login";
|
||||||
|
import ResetPassword from "../pages/ResetPassword";
|
||||||
const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => {
|
const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => {
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
@@ -35,6 +38,8 @@ const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => {
|
|||||||
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}>
|
||||||
|
{modalContent === "join" && <Join />}
|
||||||
|
{modalContent === "reset-password" && <ResetPassword />}
|
||||||
{modalContent === "req_notification" && <NotificationRequest setModal={setModal} />}
|
{modalContent === "req_notification" && <NotificationRequest setModal={setModal} />}
|
||||||
{modalContent === "blocked_notification" && <NotificationBlocked />}
|
{modalContent === "blocked_notification" && <NotificationBlocked />}
|
||||||
{modalContent === "create_clerk" && <CreateClerk shopId={shop.cafeId} />}
|
{modalContent === "create_clerk" && <CreateClerk shopId={shop.cafeId} />}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ const MusicComponent = ({ song, min, max, onDecision }) => {
|
|||||||
<p className="artist-name">{song.artist}</p>
|
<p className="artist-name">{song.artist}</p>
|
||||||
{min < 0 && <p className="artist-name"><--- {song.disagree} no - {song.agree} yes ---></p>}
|
{min < 0 && <p className="artist-name"><--- {song.disagree} no - {song.agree} yes ---></p>}
|
||||||
</div>
|
</div>
|
||||||
<p className="song-duration">{formatDuration(song.duration_ms)}</p>
|
<p className="song-duration">{song.length}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import API_BASE_URL from "../config.js";
|
|||||||
import "./MusicPlayer.css";
|
import "./MusicPlayer.css";
|
||||||
import MusicComponent from "./MusicComponent";
|
import MusicComponent from "./MusicComponent";
|
||||||
|
|
||||||
export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
|
export function MusicPlayer({ socket, shopId, user, shopOwnerId, isSpotifyNeedLogin, queue }) {
|
||||||
const [currentTime, setCurrentTime] = useState(0);
|
const [currentTime, setCurrentTime] = useState(0);
|
||||||
const [trackLength, setTrackLength] = useState(0);
|
const [trackLength, setTrackLength] = useState(0);
|
||||||
const [viewing, setViewing] = useState(false); // State for expansion
|
const [viewing, setViewing] = useState(false); // State for expansion
|
||||||
@@ -13,7 +13,6 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
|
|||||||
const [debouncedSongName, setDebouncedSongName] = useState(songName);
|
const [debouncedSongName, setDebouncedSongName] = useState(songName);
|
||||||
const [currentSong, setCurrentSong] = useState([]);
|
const [currentSong, setCurrentSong] = useState([]);
|
||||||
const [songs, setSongs] = useState([]);
|
const [songs, setSongs] = useState([]);
|
||||||
const [queue, setQueue] = useState([]);
|
|
||||||
const [paused, setPaused] = useState([]);
|
const [paused, setPaused] = useState([]);
|
||||||
|
|
||||||
const [lyrics, setLyrics] = useState([]);
|
const [lyrics, setLyrics] = useState([]);
|
||||||
@@ -111,6 +110,11 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
|
|||||||
fetchColor();
|
fetchColor();
|
||||||
}, [currentSong]);
|
}, [currentSong]);
|
||||||
|
|
||||||
|
const convertToMilliseconds = (timeStr) => {
|
||||||
|
const [minutes, seconds] = timeStr.split(':').map(Number);
|
||||||
|
return (minutes * 60 + seconds) * 1000;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
|
|
||||||
@@ -123,13 +127,9 @@ export function MusicPlayer({ socket, shopId, user, isSpotifyNeedLogin }) {
|
|||||||
setCurrentSong(response);
|
setCurrentSong(response);
|
||||||
setCurrentTime(response.progress_ms / 1000); // Convert milliseconds to seconds
|
setCurrentTime(response.progress_ms / 1000); // Convert milliseconds to seconds
|
||||||
setLyricProgressMs(response.progress_ms);
|
setLyricProgressMs(response.progress_ms);
|
||||||
setTrackLength(response.item.duration_ms / 1000);
|
setTrackLength(convertToMilliseconds(response.item.length));
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("updateQueue", (response) => {
|
|
||||||
setQueue(response);
|
|
||||||
console.log(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("updatePlayer", (response) => {
|
socket.on("updatePlayer", (response) => {
|
||||||
setPaused(response.decision);
|
setPaused(response.decision);
|
||||||
@@ -180,6 +180,25 @@ useEffect(() => {
|
|||||||
// Listen for the "updateCanvas" event
|
// Listen for the "updateCanvas" event
|
||||||
socket.on("updateCanvas", handleUpdateCanvas);
|
socket.on("updateCanvas", handleUpdateCanvas);
|
||||||
|
|
||||||
|
socket.on("claimPlayerRes", (response) => {
|
||||||
|
if (response.error) {
|
||||||
|
console.log('Error:', response.error);
|
||||||
|
// Handle error
|
||||||
|
} else {
|
||||||
|
window.open(response.url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("unClaimPlayerRes", (response) => {
|
||||||
|
if (response.error) {
|
||||||
|
console.log('Error:', response.error);
|
||||||
|
// Handle error
|
||||||
|
} else {
|
||||||
|
console.log('Player token:', response.token);
|
||||||
|
// Handle success and use the player token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Clean up the socket listener when the component is unmounted
|
// Clean up the socket listener when the component is unmounted
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("updateCanvas", handleUpdateCanvas);
|
socket.off("updateCanvas", handleUpdateCanvas);
|
||||||
@@ -235,10 +254,10 @@ useEffect(() => {
|
|||||||
setSongName(event.target.value);
|
setSongName(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRequest = (trackId) => {
|
const onRequest = (track) => {
|
||||||
const token = localStorage.getItem("auth");
|
const token = localStorage.getItem("auth");
|
||||||
if (socket != null && token) {
|
if (socket != null && token) {
|
||||||
socket.emit("songRequest", { token, shopId, trackId });
|
socket.emit("songRequest", { token, shopId, track });
|
||||||
setSongName("");
|
setSongName("");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -262,19 +281,20 @@ useEffect(() => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSpotifyAuth = () => {
|
const handleSetPlayer = () => {
|
||||||
const token = localStorage.getItem("auth");
|
const token = localStorage.getItem("auth");
|
||||||
let nextUrl = ""; // Use 'let' since the value will change
|
|
||||||
if (isSpotifyNeedLogin) {
|
|
||||||
nextUrl = API_BASE_URL + `/login?token=${token}&cafeId=${shopId}`;
|
|
||||||
} else {
|
|
||||||
nextUrl = API_BASE_URL + `/logout?token=${token}&cafeId=${shopId}`;
|
|
||||||
}
|
|
||||||
window.location.href = nextUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogin = () => {
|
if (isSpotifyNeedLogin) {
|
||||||
// navigate(`/login/${shopId}`);
|
socket.emit("claimPlayer", {
|
||||||
|
token,
|
||||||
|
shopId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.emit("unClaimPlayer", {
|
||||||
|
token,
|
||||||
|
shopId,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -432,14 +452,14 @@ useEffect(() => {
|
|||||||
className={`expandable-container ${expanded ? "expanded" : ""}`}
|
className={`expandable-container ${expanded ? "expanded" : ""}`}
|
||||||
ref={expandableContainerRef}
|
ref={expandableContainerRef}
|
||||||
>
|
>
|
||||||
{user.cafeId != null && user.cafeId == shopId && (
|
{user.cafeId == shopId || user.userId == shopOwnerId && (
|
||||||
<div className="auth-box">
|
<div className="auth-box">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={
|
placeholder={
|
||||||
isSpotifyNeedLogin ? "Login Spotify" : "Logout Spotify"
|
isSpotifyNeedLogin ? "Set as music player" : "Unset as music player"
|
||||||
}
|
}
|
||||||
onClick={handleSpotifyAuth}
|
onClick={handleSetPlayer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -466,11 +486,14 @@ useEffect(() => {
|
|||||||
song={song}
|
song={song}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
onDecision={(e) => onRequest(song.trackId)}
|
onDecision={(e) => onRequest(song)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{songName == "" &&
|
{
|
||||||
queue.length > 0 &&
|
songName === "" &&
|
||||||
|
queue &&
|
||||||
|
Array.isArray(queue) &&
|
||||||
|
queue.length > 0 && (
|
||||||
queue.map((song, index) => (
|
queue.map((song, index) => (
|
||||||
<MusicComponent
|
<MusicComponent
|
||||||
key={index}
|
key={index}
|
||||||
@@ -479,7 +502,9 @@ useEffect(() => {
|
|||||||
max={100}
|
max={100}
|
||||||
onDecision={(vote) => onDecision(song.trackId, vote)}
|
onDecision={(vote) => onDecision(song.trackId, vote)}
|
||||||
/>
|
/>
|
||||||
))}
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
{songName == "" && queue.length < 1 && (
|
{songName == "" && queue.length < 1 && (
|
||||||
<div className="rectangle">
|
<div className="rectangle">
|
||||||
<div className="diagonal-text">No Beats Ahead - Drop Your Hits</div>
|
<div className="diagonal-text">No Beats Ahead - Drop Your Hits</div>
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
/* src/RouletteWheel.css */
|
|
||||||
/* html,
|
|
||||||
body,
|
|
||||||
img {
|
|
||||||
overflow: hidden;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.roulette-wheel-container {
|
|
||||||
position: fixed;
|
|
||||||
left: 50%;
|
|
||||||
/* Center the container horizontally */
|
|
||||||
top: 30%;
|
|
||||||
transform: translate(-40%, 20%);
|
|
||||||
scale: 3;
|
|
||||||
max-width: 100vw;
|
|
||||||
/* Limit container width to viewport width */
|
|
||||||
max-height: 100vh;
|
|
||||||
/* Limit container height to viewport height */
|
|
||||||
overflow: hidden;
|
|
||||||
/* Hide overflowing content */
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
pointer-events: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
/* Safari */
|
|
||||||
-ms-user-select: none;
|
|
||||||
/* IE 10 and IE 11 */
|
|
||||||
user-select: none;
|
|
||||||
/* Standard syntax */
|
|
||||||
}
|
|
||||||
|
|
||||||
.roulette-wheel {
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
pointer-events: auto;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: transform 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roulette-image {
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
user-select: none;
|
|
||||||
/* Prevents the image from being selected */
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roulette-input {
|
|
||||||
position: absolute;
|
|
||||||
width: 30%;
|
|
||||||
/* Increase size for better visibility */
|
|
||||||
height: auto;
|
|
||||||
/* Increase size for better visibility */
|
|
||||||
border: none;
|
|
||||||
/* Remove border for simplicity */
|
|
||||||
border-radius: 5px;
|
|
||||||
/* Add border radius for rounded corners */
|
|
||||||
color: rgb(2, 2, 2);
|
|
||||||
/* Text color */
|
|
||||||
font-size: 16px;
|
|
||||||
/* Font size */
|
|
||||||
border: 2px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roulette-button {
|
|
||||||
z-index: 100;
|
|
||||||
position: absolute;
|
|
||||||
width: 30%;
|
|
||||||
height: auto;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
color: rgb(2, 2, 2);
|
|
||||||
font-size: 16px;
|
|
||||||
border: 2px solid #ccc;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roulette-button:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roulette-button.error {
|
|
||||||
background-color: red; /* Change button color on error */
|
|
||||||
transition: background-color 0.5s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
|
||||||
import "./RouletteWheel.css";
|
|
||||||
import coffeeImage from "./coffee.png"; // Update the path to your image
|
|
||||||
|
|
||||||
import { ThreeDots } from "react-loader-spinner";
|
|
||||||
|
|
||||||
const RouletteWheel = ({ isForRegister, onSignIn, onSignUp }) => {
|
|
||||||
const [rotation, setRotation] = useState(0);
|
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
|
||||||
const startAngleRef = useRef(0);
|
|
||||||
const startRotationRef = useRef(0);
|
|
||||||
const wheelRef = useRef(null);
|
|
||||||
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [username, setUsername] = useState("");
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [error, setError] = useState(false);
|
|
||||||
|
|
||||||
const emailInputRef = useRef(null);
|
|
||||||
const usernameInputRef = useRef(null);
|
|
||||||
const passwordInputRef = useRef(null);
|
|
||||||
|
|
||||||
const handleSignIn = () => {
|
|
||||||
setError(false); // Reset error state before login attempt
|
|
||||||
onSignIn(email, username, password, setLoading, setError);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSignUp = () => {
|
|
||||||
setError(false); // Reset error state before login attempt
|
|
||||||
onSignUp(email, username, password, setLoading, setError);
|
|
||||||
};
|
|
||||||
const handleStart = (x, y) => {
|
|
||||||
setIsDragging(true);
|
|
||||||
startAngleRef.current = getAngle(x, y);
|
|
||||||
startRotationRef.current = rotation;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setError(false);
|
|
||||||
}, [error]);
|
|
||||||
|
|
||||||
const handleMove = (x, y) => {
|
|
||||||
if (isDragging) {
|
|
||||||
const angle = getAngle(x, y);
|
|
||||||
const deltaAngle = angle - startAngleRef.current;
|
|
||||||
setRotation(startRotationRef.current + deltaAngle);
|
|
||||||
if (isForRegister) {
|
|
||||||
if (rotation + deltaAngle > 30 || rotation + deltaAngle < -210)
|
|
||||||
handleEnd();
|
|
||||||
} else {
|
|
||||||
if (rotation + deltaAngle > 30 || rotation + deltaAngle < -120)
|
|
||||||
handleEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEnd = () => {
|
|
||||||
setIsDragging(false);
|
|
||||||
setRotation((prevRotation) => {
|
|
||||||
const snappedRotation = Math.round(prevRotation / 90) * 90;
|
|
||||||
return snappedRotation;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseDown = (e) => {
|
|
||||||
handleStart(e.clientX, e.clientY);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseMove = (e) => {
|
|
||||||
handleMove(e.clientX, e.clientY);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
|
||||||
handleEnd();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTouchStart = (e) => {
|
|
||||||
const touch = e.touches[0];
|
|
||||||
handleStart(touch.clientX, touch.clientY);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTouchMove = (e) => {
|
|
||||||
if (!isDragging) return;
|
|
||||||
e.preventDefault();
|
|
||||||
const touch = e.touches[0];
|
|
||||||
handleMove(touch.clientX, touch.clientY);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTouchEnd = (e) => {
|
|
||||||
handleEnd();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChildMouseDown = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChildTouchStart = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAngle = (x, y) => {
|
|
||||||
const rect = wheelRef.current.getBoundingClientRect();
|
|
||||||
const centerX = rect.left + rect.width / 2;
|
|
||||||
const centerY = rect.top + rect.height / 2;
|
|
||||||
const dx = x - centerX;
|
|
||||||
const dy = y - centerY;
|
|
||||||
return Math.atan2(dy, dx) * (180 / Math.PI);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isDragging) {
|
|
||||||
document.addEventListener("mousemove", handleMouseMove);
|
|
||||||
document.addEventListener("mouseup", handleMouseUp);
|
|
||||||
document.addEventListener("touchmove", handleTouchMove, {
|
|
||||||
passive: false,
|
|
||||||
});
|
|
||||||
document.addEventListener("touchend", handleTouchEnd, { passive: false });
|
|
||||||
} else {
|
|
||||||
document.removeEventListener("mousemove", handleMouseMove);
|
|
||||||
document.removeEventListener("mouseup", handleMouseUp);
|
|
||||||
document.removeEventListener("touchmove", handleTouchMove);
|
|
||||||
document.removeEventListener("touchend", handleTouchEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousemove", handleMouseMove);
|
|
||||||
document.removeEventListener("mouseup", handleMouseUp);
|
|
||||||
document.removeEventListener("touchmove", handleTouchMove);
|
|
||||||
document.removeEventListener("touchend", handleTouchEnd);
|
|
||||||
};
|
|
||||||
}, [isDragging]);
|
|
||||||
|
|
||||||
const inputPositions = [-90, 0, 90, 180]; // Positions for the inputs
|
|
||||||
|
|
||||||
const isVisible = (angle) => {
|
|
||||||
const modAngle = ((angle % 360) + 360) % 360;
|
|
||||||
return modAngle % 90 === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isForRegister) {
|
|
||||||
if (isVisible(rotation % 360 !== -0)) {
|
|
||||||
emailInputRef.current.focus();
|
|
||||||
} else if (isVisible(rotation % 360 !== -90)) {
|
|
||||||
usernameInputRef.current.focus();
|
|
||||||
} else if (isVisible(rotation % 360 !== -180)) {
|
|
||||||
passwordInputRef.current.focus();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isVisible(rotation % 360 !== -0)) {
|
|
||||||
usernameInputRef.current.focus();
|
|
||||||
} else if (isVisible(rotation % 360 !== -90)) {
|
|
||||||
passwordInputRef.current.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [rotation]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="roulette-wheel-container">
|
|
||||||
<div
|
|
||||||
className="roulette-wheel"
|
|
||||||
ref={wheelRef}
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onTouchStart={handleTouchStart}
|
|
||||||
style={{ transform: `rotate(${rotation}deg)` }}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className={`roulette-input ${
|
|
||||||
isVisible(rotation % 360 !== -0) ? "" : "hidden"
|
|
||||||
}`}
|
|
||||||
placeholder="Username"
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
|
||||||
ref={usernameInputRef}
|
|
||||||
style={{ transform: "translate(60%, -220%) rotate(0deg)" }}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className={`roulette-input ${
|
|
||||||
isVisible(rotation % 360 !== -0) ? "" : "hidden"
|
|
||||||
}`}
|
|
||||||
placeholder="Password"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
ref={passwordInputRef}
|
|
||||||
style={{ transform: "translate(90%, -90%) rotate(0deg)" }}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className={`roulette-button ${error ? "error" : ""} ${
|
|
||||||
isVisible(rotation % 360 !== -0) ? "" : "hidden"
|
|
||||||
}`}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={handleSignIn}
|
|
||||||
onMouseDown={handleChildMouseDown}
|
|
||||||
onTouchStart={handleChildTouchStart}
|
|
||||||
style={{ transform: "translate(110%, -10%) rotate(0deg)" }}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<ThreeDots color="black" height={15} width={40} /> // Show loader when loading
|
|
||||||
) : (
|
|
||||||
"Sign in"
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
className={`roulette-input ${
|
|
||||||
isVisible(rotation % 360 !== -90) ? "" : "hidden"
|
|
||||||
}`}
|
|
||||||
placeholder="Email"
|
|
||||||
value={email}
|
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
ref={emailInputRef}
|
|
||||||
style={{ transform: "translate(30%, 320%) rotate(90deg)" }}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className={`roulette-input ${
|
|
||||||
isVisible(rotation % 360 !== -90) ? "" : "hidden"
|
|
||||||
}`}
|
|
||||||
placeholder="Username"
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
|
||||||
ref={usernameInputRef}
|
|
||||||
style={{ transform: "translate(10%, 320%) rotate(90deg)" }}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className={`roulette-input ${
|
|
||||||
isVisible(rotation % 360 !== -90) ? "" : "hidden"
|
|
||||||
}`}
|
|
||||||
placeholder="Password"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
ref={passwordInputRef}
|
|
||||||
style={{ transform: "translate(-10%, 320%) rotate(90deg)" }}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className={`roulette-button ${error ? "error" : ""} ${
|
|
||||||
isVisible(rotation % 360 !== -90) ? "" : "hidden"
|
|
||||||
} `}
|
|
||||||
onClick={handleSignUp}
|
|
||||||
onMouseDown={handleChildMouseDown}
|
|
||||||
onTouchStart={handleChildTouchStart}
|
|
||||||
style={{ transform: "translate(-60%, 320%) rotate(90deg)" }}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<ThreeDots color="black" height={15} width={40} /> // Show loader when loading
|
|
||||||
) : (
|
|
||||||
"Sign Up"
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<img src={coffeeImage} className="roulette-image" alt="Coffee" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RouletteWheel;
|
|
||||||
@@ -1,108 +1,39 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState } from 'react';
|
||||||
import YouTube from 'react-youtube';
|
import YouTube from 'react-youtube';
|
||||||
|
|
||||||
export function TrackPlayer({ next }) {
|
export function TrackPlayer({ next, queueList, setQueueList }) {
|
||||||
// State to store the progress in milliseconds, video duration, and the next video ID
|
// Initial list of video IDs (YouTube video IDs)
|
||||||
const [progress, setProgress] = useState(0);
|
const videos = [
|
||||||
const [duration, setDuration] = useState(0);
|
'dQw4w9WgXcQ', // Example Video 1
|
||||||
const [currentTrack, setCurrentTrack] = useState(null); // Initial video ID
|
'tgbNymZ7vqY', // Example Video 2
|
||||||
const [nextTrack, setNextTrack] = useState(null); // Initial video ID
|
'oHg5SJYRHA0', // Example Video 3
|
||||||
const [isNearEnd, setIsNearEnd] = useState(false); // Flag for 20 seconds left
|
];
|
||||||
const playerRef = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// State to track the current video index
|
||||||
if (next == null) return;
|
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
|
||||||
if (currentTrack == null) setCurrentTrack(next);
|
|
||||||
setNextTrack(next);
|
|
||||||
}, [next]);
|
|
||||||
|
|
||||||
const handlePlayerStateChange = (event) => {
|
// Function to handle video end event
|
||||||
if (event.data === window.YT.PlayerState.PLAYING) {
|
const handleVideoEnd = () => {
|
||||||
|
// Update to next video or loop to the first video if at the end
|
||||||
// Start tracking progress once the video starts playing
|
setCurrentVideoIndex((prevIndex) => (prevIndex + 1) % videos.length);
|
||||||
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) => {
|
// YouTube video player options
|
||||||
playerRef.current = event.target;
|
const opts = {
|
||||||
const durationInSeconds = playerRef.current.getDuration(); // Get video duration in seconds
|
height: '390',
|
||||||
setDuration(durationInSeconds * 1000); // Set the duration in milliseconds
|
width: '640',
|
||||||
|
playerVars: {
|
||||||
// Set the video quality to the lowest available (typically 360p or smaller)
|
autoplay: 1, // Automatically play the video
|
||||||
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 (
|
return (
|
||||||
<div className="App" style={{visibility: 'hidden', position: 'fixed'}}>
|
|
||||||
{currentTrack != null && (
|
|
||||||
<div>
|
<div>
|
||||||
<YouTube
|
<YouTube
|
||||||
videoId={currentTrack.videoId} // Dynamically change video based on currentTrack
|
videoId={videos[currentVideoIndex]} // Use the current video ID
|
||||||
opts={{
|
opts={opts}
|
||||||
height: '315',
|
onEnd={handleVideoEnd} // Call handleVideoEnd when the video ends
|
||||||
width: '560',
|
|
||||||
playerVars: {
|
|
||||||
autoplay: 1,
|
|
||||||
controls: 1,
|
|
||||||
mute: 0,
|
|
||||||
loop: 0, // Do not loop; handle next video manually
|
|
||||||
quality: 'small', // Request small quality (360p or lower)
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onReady={handlePlayerReady} // Get duration and set quality on ready
|
|
||||||
onStateChange={handlePlayerStateChange}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
<div>Progress: {progress} ms</div>
|
|
||||||
<div>Video Duration: {duration} ms</div>
|
|
||||||
<div>
|
|
||||||
{isNearEnd && <div>Video is near the end (20 seconds left)</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export async function getTransaction(transactionId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMyTransactions(shopId, demand) {
|
export async function getMyTransactions() {
|
||||||
try {
|
try {
|
||||||
const token = getLocalStorage("auth");
|
const token = getLocalStorage("auth");
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -163,9 +163,6 @@ export async function getMyTransactions(shopId, demand) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const transactions = await response.json();
|
const transactions = await response.json();
|
||||||
return transactions;
|
return transactions;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function CafePage({
|
|||||||
removeConnectedGuestSides,
|
removeConnectedGuestSides,
|
||||||
setModal,
|
setModal,
|
||||||
loading,
|
loading,
|
||||||
|
queue
|
||||||
}) {
|
}) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@@ -87,6 +88,7 @@ function CafePage({
|
|||||||
}
|
}
|
||||||
checkWelcomePageConfig();
|
checkWelcomePageConfig();
|
||||||
}, [welcomePageConfig]);
|
}, [welcomePageConfig]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user.cafeId != null && user.cafeId !== shopId) {
|
if (user.cafeId != null && user.cafeId !== shopId) {
|
||||||
// Preserve existing query parameters
|
// Preserve existing query parameters
|
||||||
@@ -232,7 +234,9 @@ function CafePage({
|
|||||||
socket={socket}
|
socket={socket}
|
||||||
shopId={shopId}
|
shopId={shopId}
|
||||||
user={user}
|
user={user}
|
||||||
|
shopOwnerId={shopOwnerId}
|
||||||
isSpotifyNeedLogin={isSpotifyNeedLogin}
|
isSpotifyNeedLogin={isSpotifyNeedLogin}
|
||||||
|
queue={queue}
|
||||||
/>
|
/>
|
||||||
<ItemTypeLister
|
<ItemTypeLister
|
||||||
user={user}
|
user={user}
|
||||||
|
|||||||
194
src/pages/Dashboard copy.js
Normal file
194
src/pages/Dashboard copy.js
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import styles from "./Dashboard.module.css"; // Import module CSS for styling
|
||||||
|
import Header from "../components/Header";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import AccountUpdateModal from "../components/AccountUpdateModal";
|
||||||
|
import { removeLocalStorage } from "../helpers/localStorageHelpers";
|
||||||
|
import { getAllCafeOwner, createCafeOwner } from "../helpers/userHelpers";
|
||||||
|
import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers";
|
||||||
|
|
||||||
|
import { ThreeDots } from "react-loader-spinner";
|
||||||
|
import { unsubscribeUser } from "../helpers/subscribeHelpers.js";
|
||||||
|
|
||||||
|
const Dashboard = ({ user, setModal }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [items, setItems] = useState([]);
|
||||||
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
const [newItem, setNewItem] = useState({ name: "", type: "" });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && user.roleId === 0) {
|
||||||
|
setLoading(true);
|
||||||
|
getAllCafeOwner()
|
||||||
|
.then((data) => {
|
||||||
|
setItems(data);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error fetching cafe owners:", error);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (user && user.roleId === 1) {
|
||||||
|
setLoading(true);
|
||||||
|
getOwnedCafes(user.userId)
|
||||||
|
.then((data) => {
|
||||||
|
setItems(data);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error fetching owned cafes:", error);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
removeLocalStorage("auth");
|
||||||
|
unsubscribeUser();
|
||||||
|
navigate(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateItem = () => {
|
||||||
|
if (user.roleId < 1) {
|
||||||
|
// Create admin functionality
|
||||||
|
createCafeOwner(newItem.email, newItem.username, newItem.password)
|
||||||
|
.then((newitem) => {
|
||||||
|
setItems([...items, { userId: newitem.userId, name: newitem.username }]);
|
||||||
|
setIsCreating(false);
|
||||||
|
setNewItem({ name: "", type: "" });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error creating admin:", error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create cafe functionality
|
||||||
|
createCafe(newItem.name)
|
||||||
|
.then((newitem) => {
|
||||||
|
setItems([...items, { cafeId: newitem.cafeId, name: newitem.name }]);
|
||||||
|
setIsCreating(false);
|
||||||
|
setNewItem({ name: "", type: "" });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error creating cafe:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// function calculateCafeMetrics(cafes) {
|
||||||
|
// let totalIncomeThisMonth = 0;
|
||||||
|
// let totalIncomeLastMonth = 0;
|
||||||
|
|
||||||
|
// cafes.forEach(cafe => {
|
||||||
|
// const currentIncome = cafe.totalIncome;
|
||||||
|
// const growth = cafe.growthIncome / 100;
|
||||||
|
|
||||||
|
// // Hitung keuntungan bulan lalu
|
||||||
|
// const lastMonthIncome = currentIncome / (1 + growth);
|
||||||
|
|
||||||
|
// // Tambahkan ke total
|
||||||
|
// totalIncomeThisMonth += currentIncome;
|
||||||
|
// totalIncomeLastMonth += lastMonthIncome;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Hitung growth total
|
||||||
|
// const totalGrowth = ((totalIncomeThisMonth - totalIncomeLastMonth) / totalIncomeLastMonth) * 100;
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// totalIncomeThisMonth,
|
||||||
|
// totalIncomeLastMonth: totalIncomeLastMonth.toFixed(2),
|
||||||
|
// totalGrowth: totalGrowth.toFixed(2)
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header
|
||||||
|
HeaderText={"kedaimaster"}
|
||||||
|
isEdit={() => setIsModalOpen(true)}
|
||||||
|
isLogout={handleLogout}
|
||||||
|
user={user}
|
||||||
|
showProfile={true}
|
||||||
|
setModal={setModal}
|
||||||
|
/>
|
||||||
|
{user && user.roleId < 2 && (
|
||||||
|
<div className={styles.dashboard}>
|
||||||
|
{loading && <ThreeDots />}
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
onClick={() => navigate("/" + item.cafeId)}
|
||||||
|
className={styles.rectangle}
|
||||||
|
>
|
||||||
|
<h1>{item.name || item.username}</h1>
|
||||||
|
<div><h1>{item.report?.totalIncome}</h1></div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{user && user.roleId < 1 ? (
|
||||||
|
<div
|
||||||
|
className={styles.rectangle}
|
||||||
|
onClick={() => setIsCreating(true)}
|
||||||
|
>
|
||||||
|
Create Client
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={styles.rectangle}
|
||||||
|
onClick={() => setIsCreating(true)}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{user.username && (
|
||||||
|
<AccountUpdateModal
|
||||||
|
user={user}
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={handleModalClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isCreating && (
|
||||||
|
<div className={styles.createModal}>
|
||||||
|
<h2>Create New {user.roleId < 1 ? "Admin" : "Cafe"}</h2>
|
||||||
|
{user.roleId < 1 ?<>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={newItem.email}
|
||||||
|
onChange={(e) => setNewItem({ ...newItem, email: e.target.value })}
|
||||||
|
placeholder="email"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newItem.username}
|
||||||
|
onChange={(e) => setNewItem({ ...newItem, username: e.target.value })}
|
||||||
|
placeholder="username"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={newItem.password}
|
||||||
|
onChange={(e) => setNewItem({ ...newItem, password: e.target.value })}
|
||||||
|
placeholder="Password"
|
||||||
|
/></> :
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newItem.name}
|
||||||
|
onChange={(e) => setNewItem({ ...newItem, name: e.target.value })}
|
||||||
|
placeholder="Name"
|
||||||
|
/>}
|
||||||
|
<button onClick={handleCreateItem}>Create</button>
|
||||||
|
<button onClick={() => setIsCreating(false)}>Cancel</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
@@ -1,22 +1,93 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react';
|
||||||
import styles from "./Dashboard.module.css"; // Import module CSS for styling
|
import styles from './LinktreePage.module.css'; // Import the module.css file
|
||||||
import Header from "../components/Header";
|
import { loginUser } from "../helpers/userHelpers";
|
||||||
|
import { ThreeDots } from "react-loader-spinner";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import AccountUpdateModal from "../components/AccountUpdateModal";
|
|
||||||
import { removeLocalStorage } from "../helpers/localStorageHelpers";
|
import { getLocalStorage, removeLocalStorage } from "../helpers/localStorageHelpers";
|
||||||
import { getAllCafeOwner, createCafeOwner } from "../helpers/userHelpers";
|
import { getAllCafeOwner, createCafeOwner } from "../helpers/userHelpers";
|
||||||
import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers";
|
import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers";
|
||||||
|
import { getMyTransactions } from "../helpers/transactionHelpers";
|
||||||
import { ThreeDots } from "react-loader-spinner";
|
|
||||||
import { unsubscribeUser } from "../helpers/subscribeHelpers.js";
|
import { unsubscribeUser } from "../helpers/subscribeHelpers.js";
|
||||||
|
|
||||||
const Dashboard = ({ user, setModal }) => {
|
import Header from '../components/Header';
|
||||||
|
|
||||||
|
const LinktreePage = ({ user, setModal }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [loading, setLoading] = useState(true);
|
const [inputtingPassword, setInputtingPassword] = useState(false);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [username, setUsername] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
const [newItem, setNewItem] = useState({ name: "", type: "" });
|
const [newItem, setNewItem] = useState({ name: "", type: "" });
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [expanded, setIsExpand] = useState(false);
|
||||||
|
const [expandedCafeId, setExpandedCafeId] = useState(null); // Track which cafe is expanded
|
||||||
|
|
||||||
|
const handleToggleExpand = (cafeId) => {
|
||||||
|
setExpandedCafeId(expandedCafeId === cafeId ? null : cafeId); // Toggle expand for a specific cafe
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMyTransactions = async () => {
|
||||||
|
try {
|
||||||
|
setError(false);
|
||||||
|
setLoading(true);
|
||||||
|
const response = await getMyTransactions();
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
console.log(response)
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
setError(true); // Trigger error state in the button
|
||||||
|
console.error('Login failed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError(true);
|
||||||
|
console.error('Error occurred while logging in:', error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false); // Ensure loading state is cleared
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
try {
|
||||||
|
setError(false);
|
||||||
|
setLoading(true);
|
||||||
|
const response = await loginUser(username, password);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
localStorage.setItem('auth', response.token);
|
||||||
|
|
||||||
|
if (response.cafeId !== null) {
|
||||||
|
window.location.href = response.cafeId;
|
||||||
|
} else {
|
||||||
|
let destination = '/';
|
||||||
|
window.location.href = destination;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError(true); // Trigger error state in the button
|
||||||
|
console.error('Login failed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError(true);
|
||||||
|
console.error('Error occurred while logging in:', error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false); // Ensure loading state is cleared
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
removeLocalStorage("auth");
|
||||||
|
unsubscribeUser();
|
||||||
|
navigate(0);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && user.roleId === 0) {
|
if (user && user.roleId === 0) {
|
||||||
@@ -43,18 +114,19 @@ const Dashboard = ({ user, setModal }) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (user && user.roleId == 3) {
|
||||||
|
handleMyTransactions()
|
||||||
|
.then((data) => {
|
||||||
|
setItems(data);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error fetching owned cafes:", error);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
const handleModalClose = () => {
|
|
||||||
setIsModalOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
removeLocalStorage("auth");
|
|
||||||
unsubscribeUser();
|
|
||||||
navigate(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCreateItem = () => {
|
const handleCreateItem = () => {
|
||||||
if (user.roleId < 1) {
|
if (user.roleId < 1) {
|
||||||
// Create admin functionality
|
// Create admin functionality
|
||||||
@@ -80,42 +152,10 @@ const Dashboard = ({ user, setModal }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// function calculateCafeMetrics(cafes) {
|
|
||||||
// let totalIncomeThisMonth = 0;
|
|
||||||
// let totalIncomeLastMonth = 0;
|
|
||||||
|
|
||||||
// cafes.forEach(cafe => {
|
|
||||||
// const currentIncome = cafe.totalIncome;
|
|
||||||
// const growth = cafe.growthIncome / 100;
|
|
||||||
|
|
||||||
// // Hitung keuntungan bulan lalu
|
|
||||||
// const lastMonthIncome = currentIncome / (1 + growth);
|
|
||||||
|
|
||||||
// // Tambahkan ke total
|
|
||||||
// totalIncomeThisMonth += currentIncome;
|
|
||||||
// totalIncomeLastMonth += lastMonthIncome;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Hitung growth total
|
|
||||||
// const totalGrowth = ((totalIncomeThisMonth - totalIncomeLastMonth) / totalIncomeLastMonth) * 100;
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// totalIncomeThisMonth,
|
|
||||||
// totalIncomeLastMonth: totalIncomeLastMonth.toFixed(2),
|
|
||||||
// totalGrowth: totalGrowth.toFixed(2)
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.linktreePage}>
|
||||||
<Header
|
{/* SVG Icon */}
|
||||||
HeaderText={"kedaimaster"}
|
|
||||||
isEdit={() => setIsModalOpen(true)}
|
|
||||||
isLogout={handleLogout}
|
|
||||||
user={user}
|
|
||||||
showProfile={true}
|
|
||||||
setModal={setModal}
|
|
||||||
/>
|
|
||||||
{user && user.roleId < 2 && (
|
{user && user.roleId < 2 && (
|
||||||
<div className={styles.dashboard}>
|
<div className={styles.dashboard}>
|
||||||
{loading && <ThreeDots />}
|
{loading && <ThreeDots />}
|
||||||
@@ -146,49 +186,204 @@ const Dashboard = ({ user, setModal }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(user.length == 0 || user.roleId > 1) &&
|
||||||
|
<>
|
||||||
|
<div className={styles.dashboardLine}></div>
|
||||||
|
|
||||||
{user.username && (
|
<div className={styles.dashboardContainer}>
|
||||||
<AccountUpdateModal
|
{/* Main Heading */}
|
||||||
user={user}
|
<div className={styles.mainHeading}>
|
||||||
isOpen={isModalOpen}
|
COBA KEDAIMASTER
|
||||||
onClose={handleModalClose}
|
<div className={styles.swipeContainer}>
|
||||||
/>
|
<div className={styles.swipeContent}>
|
||||||
)}
|
<div className={styles.swipeItem}>pemesanan langsung dari meja</div>
|
||||||
|
<div className={styles.swipeItem}>pengelolaan pesanan dan keuangan</div>
|
||||||
|
<div className={styles.swipeItem}>tentukan suasana musik</div>
|
||||||
|
<div className={styles.swipeItem}>pengelolaan stok dan manajemen</div>
|
||||||
|
<div className={styles.swipeItem}>jangan pernah ragukan pelanggan</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
diskon 0%
|
||||||
|
</div>
|
||||||
|
|
||||||
{isCreating && (
|
{/* Sub Heading */}
|
||||||
<div className={styles.createModal}>
|
<div className={styles.subHeading}>
|
||||||
<h2>Create New {user.roleId < 1 ? "Admin" : "Cafe"}</h2>
|
Solusi berbasis web untuk memudahkan pengelolaan kedai, dengan fitur yang mempermudah pemilik, kasir, dan tamu berinteraksi.
|
||||||
{user.roleId < 1 ?<>
|
</div>
|
||||||
|
{getLocalStorage('auth') == null &&
|
||||||
|
<div className={styles.LoginForm}>
|
||||||
|
<div className={`${styles.FormUsername} ${inputtingPassword ? styles.animateForm : styles.reverseForm}`}>
|
||||||
|
<label htmlFor="username" className={styles.usernameLabel}>
|
||||||
|
---- masuk -------------------------------
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
id="username"
|
||||||
value={newItem.email}
|
|
||||||
onChange={(e) => setNewItem({ ...newItem, email: e.target.value })}
|
|
||||||
placeholder="email"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={newItem.username}
|
|
||||||
onChange={(e) => setNewItem({ ...newItem, username: e.target.value })}
|
|
||||||
placeholder="username"
|
placeholder="username"
|
||||||
|
maxLength="30"
|
||||||
|
className={!error ? styles.usernameInput : styles.usernameInputError}
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<button onClick={() => setInputtingPassword(true)} className={styles.claimButton}>
|
||||||
|
<span>➜</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`${styles.FormPassword} ${inputtingPassword ? styles.animateForm : styles.reverseForm}`}>
|
||||||
|
<span>
|
||||||
|
<label onClick={() => setInputtingPassword(false)} htmlFor="password" className={styles.usernameLabel}>
|
||||||
|
<--- <-- kembali
|
||||||
|
</label><label htmlFor="password" className={styles.usernameLabel}>
|
||||||
|
------
|
||||||
|
</label><label onClick={() => setModal('reset-password', { username: username })} htmlFor="password" className={styles.usernameLabel}>
|
||||||
|
lupa password? -
|
||||||
|
</label></span>
|
||||||
<input
|
<input
|
||||||
|
id="password"
|
||||||
|
placeholder="password"
|
||||||
type="password"
|
type="password"
|
||||||
value={newItem.password}
|
maxLength="30"
|
||||||
onChange={(e) => setNewItem({ ...newItem, password: e.target.value })}
|
className={!error ? styles.usernameInput : styles.usernameInputError}
|
||||||
placeholder="Password"
|
value={password}
|
||||||
/></> :
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
<input
|
/>
|
||||||
type="text"
|
<button
|
||||||
value={newItem.name}
|
onClick={handleLogin}
|
||||||
onChange={(e) => setNewItem({ ...newItem, name: e.target.value })}
|
className={`${styles.claimButton} ${loading ? styles.loading : ''}`}
|
||||||
placeholder="Name"
|
disabled={loading}
|
||||||
/>}
|
>
|
||||||
<button onClick={handleCreateItem}>Create</button>
|
<span>{loading ? 'Loading...' : 'Masuk'}</span>
|
||||||
<button onClick={() => setIsCreating(false)}>Cancel</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
{/* Footer Links */}
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<div className={styles.footerLinks}>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee/discover/trending"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Pelajari lebih lanjut
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Tentang kedaimaster.com
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.signupButton}
|
||||||
|
style={{ textDecoration: 'none' }}
|
||||||
|
onClick={() => setModal('join')}
|
||||||
|
>
|
||||||
|
Daftarkan kedaimu
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className={styles.footerImage}>
|
||||||
|
<img
|
||||||
|
style={{ height: '226px', width: '150px', objectFit: 'cover' }}
|
||||||
|
src="/kedai.png"
|
||||||
|
alt="Linktree visual"
|
||||||
|
onError={(e) => e.target.src = '/fallback-image.png'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{user.length != 0 &&
|
||||||
|
<div className={` ${expanded ? styles.userInfoExpanded : styles.userInfo}`}>
|
||||||
|
<div onClick={() => setIsExpand(!expanded)} className={styles.userInfoExpandButton}>
|
||||||
|
{!expanded ?
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="#000000"
|
||||||
|
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
|
||||||
|
>
|
||||||
|
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path d="m 1 11 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 l 6 -6 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 6 6 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -5.292969 -5.292969 l -5.292969 5.292969 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={"#2e3436"}></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
:
|
||||||
|
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="#000000"
|
||||||
|
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
|
||||||
|
>
|
||||||
|
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||||
|
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
||||||
|
<g id="SVGRepo_iconCarrier">
|
||||||
|
<path d="m 1 5 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 5.292969 5.292969 l 5.292969 -5.292969 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 l -6 6 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -6 -6 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={"#2e3436"}></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Header
|
||||||
|
HeaderText={"Kedai yang pernah kamu kunjungi"}
|
||||||
|
HeaderSize={'4vw'}
|
||||||
|
showProfile={true}
|
||||||
|
setModal={setModal}
|
||||||
|
isLogout={handleLogout}
|
||||||
|
user={user}
|
||||||
|
/>
|
||||||
|
<div className={styles.ItemContainer} style={{height: expanded?'85%':'43.5%'}}>
|
||||||
|
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<div className={styles.Item}onClick={() => handleToggleExpand(item.cafeId)} key={index}>
|
||||||
|
{/* Render cafes */}
|
||||||
|
<div
|
||||||
|
// Toggle expansion on cafe click
|
||||||
|
className={styles.rectangle}
|
||||||
|
>
|
||||||
|
<h1>{item.name || item.username}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Render transactions if the cafe is expanded */}
|
||||||
|
{expandedCafeId === item.cafeId && item.transactions && (
|
||||||
|
<div className={styles.Item}>
|
||||||
|
{item.transactions.map((transaction, transactionIndex) => (
|
||||||
|
<div
|
||||||
|
key={transactionIndex}
|
||||||
|
className={styles.transaction}
|
||||||
|
style={{ backgroundColor: 'orange' }}
|
||||||
|
>
|
||||||
|
{transaction.detailedTransactions && transaction.detailedTransactions.map((detailedTransaction, detailedTransactionIndex) => (
|
||||||
|
<div key={detailedTransactionIndex}>
|
||||||
|
<p>Quantity: {detailedTransaction.qty}</p>
|
||||||
|
{detailedTransaction.Item && (
|
||||||
|
<div>
|
||||||
|
<h4>Item Name: {detailedTransaction.Item.name}</h4>
|
||||||
|
<p>Price: {detailedTransaction.Item.price}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Dashboard;
|
export default LinktreePage;
|
||||||
|
|||||||
65
src/pages/Join.js
Normal file
65
src/pages/Join.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styles from './Join.module.css'; // Import the module.css file
|
||||||
|
|
||||||
|
const LinktreePage = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.linktreePage}>
|
||||||
|
|
||||||
|
<div className={styles.dashboardContainer}>
|
||||||
|
{/* Main Heading */}
|
||||||
|
<div className={styles.mainHeading}>
|
||||||
|
Nikmati Kemudahan Mengelola Kafe
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sub Heading */}
|
||||||
|
<div className={styles.subHeading}>
|
||||||
|
Daftarkan kedaimu sekarang dan mulai gunakan semua fitur unggulan kami.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Form Section */}
|
||||||
|
<form className={styles.linktreeForm}>
|
||||||
|
<label htmlFor="username" className={styles.usernameLabel}>--------------------------------------------</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
placeholder="nomor whatsapp atau email"
|
||||||
|
maxLength="30"
|
||||||
|
className={styles.usernameInput}
|
||||||
|
/>
|
||||||
|
<button type="submit" className={styles.claimButton}>
|
||||||
|
<span>➜</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Footer Links */}
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<div className={styles.footerLinks}>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee/discover/trending"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Pelajari lebih lanjut
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Tentang kedaimaster.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className={styles.footerImage}>
|
||||||
|
<img
|
||||||
|
src="./laporan.png"
|
||||||
|
alt="Linktree visual"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LinktreePage;
|
||||||
161
src/pages/Join.module.css
Normal file
161
src/pages/Join.module.css
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/* General container */
|
||||||
|
.linktreePage {
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgb(232 204 88);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dashboardContainer {
|
||||||
|
z-index: 6;
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Heading */
|
||||||
|
.mainHeading {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: rgb(37, 79, 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeContainer {
|
||||||
|
height: 75px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
color: rgba(59, 130, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeContent {
|
||||||
|
animation: swipeUp 12s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeItem {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 75px;
|
||||||
|
max-width: 300px;
|
||||||
|
text-wrap: balance;
|
||||||
|
line-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Swipe Animation */
|
||||||
|
@keyframes swipeUp {
|
||||||
|
0%, 15% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
20%, 35% {
|
||||||
|
transform: translateY(-20%);
|
||||||
|
}
|
||||||
|
40%, 55% {
|
||||||
|
transform: translateY(-40%);
|
||||||
|
}
|
||||||
|
60%, 75% {
|
||||||
|
transform: translateY(-60%);
|
||||||
|
}
|
||||||
|
80%, 95% {
|
||||||
|
transform: translateY(-80%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sub Heading */
|
||||||
|
.subHeading {
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'poppins';
|
||||||
|
color: black;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
.linktreeForm {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usernameLabel {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #444;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usernameInput {
|
||||||
|
width: 250px;
|
||||||
|
height: 55px;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.claimButton {
|
||||||
|
width: 200px;
|
||||||
|
height: 45px;
|
||||||
|
background-color: #254F1A;
|
||||||
|
color: #D2E823;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 30px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.claimButton span {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerLinks {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerLink {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #254F1A;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signupButton {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #254F1A;
|
||||||
|
color: #254F1A;
|
||||||
|
padding: 12px 30px;
|
||||||
|
border-radius: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerImage {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerImage img {
|
||||||
|
width: 150px;
|
||||||
|
height: 226px;
|
||||||
|
margin-top: -50px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
329
src/pages/LinktreePage.module.css
Normal file
329
src/pages/LinktreePage.module.css
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
/* General container */
|
||||||
|
.linktreePage {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgb(210, 232, 35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboardLine {
|
||||||
|
position: fixed;
|
||||||
|
left: 0px;
|
||||||
|
height: 100vh;
|
||||||
|
width: 30px;
|
||||||
|
border-right: 1px solid black;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboardContainer {
|
||||||
|
background-color: rgb(210, 232, 35);
|
||||||
|
z-index: 6;
|
||||||
|
padding: 0 1rem;
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Heading */
|
||||||
|
.mainHeading {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: rgb(37, 79, 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeContainer {
|
||||||
|
height: 75px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
color: rgb(59 130 246);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeContent {
|
||||||
|
animation: swipeUp 12s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeItem {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 75px;
|
||||||
|
max-width: 300px;
|
||||||
|
text-wrap: balance;
|
||||||
|
line-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Swipe Animation */
|
||||||
|
@keyframes swipeUp {
|
||||||
|
0%, 15% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
20%, 35% {
|
||||||
|
transform: translateY(-20%);
|
||||||
|
}
|
||||||
|
40%, 55% {
|
||||||
|
transform: translateY(-40%);
|
||||||
|
}
|
||||||
|
60%, 75% {
|
||||||
|
transform: translateY(-60%);
|
||||||
|
}
|
||||||
|
80%, 95% {
|
||||||
|
transform: translateY(-80%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sub Heading */
|
||||||
|
.subHeading {
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'poppins';
|
||||||
|
color: black;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.LoginForm {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
height: 148px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
.FormUsername {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: absolute;
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormUsername.animateForm {
|
||||||
|
animation: FormUsernameProgress 0.5s forwards; /* Apply the animation when inputtingPassword is true */
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormUsername.reverseForm {
|
||||||
|
animation: FormUsernameReverse 0.5s forwards; /* Reverse animation when inputtingPassword is false */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes FormUsernameProgress {
|
||||||
|
0% {
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: -100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes FormUsernameReverse {
|
||||||
|
0% {
|
||||||
|
left: -100vw;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormPassword {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: absolute;
|
||||||
|
left: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormPassword.animateForm {
|
||||||
|
animation: FormPasswordProgress 0.5s forwards; /* Apply the animation when inputtingPassword is true */
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormPassword.reverseForm {
|
||||||
|
animation: FormPasswordReverse 0.5s forwards; /* Reverse animation when inputtingPassword is false */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes FormPasswordProgress {
|
||||||
|
0% {
|
||||||
|
left: 100vw;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes FormPasswordReverse {
|
||||||
|
0% {
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.usernameLabel {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #444;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usernameInput {
|
||||||
|
width: 250px;
|
||||||
|
height: 55px;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usernameInputError {
|
||||||
|
width: 250px;
|
||||||
|
height: 55px;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 2px solid red; /* Red border when error is true */
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
/* Apply keyframe animation for border color transition */
|
||||||
|
animation: borderTransition 2s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyframe animation for border color transition */
|
||||||
|
@keyframes borderTransition {
|
||||||
|
0% {
|
||||||
|
border-color: red; /* Initial red border */
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
border-color: transparent; /* Transition to transparent */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claimButton {
|
||||||
|
width: 200px;
|
||||||
|
height: 45px;
|
||||||
|
background-color: #254F1A;
|
||||||
|
color: #D2E823;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 30px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.claimButton span {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerLinks {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerLink {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #254F1A;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signupButton {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #254F1A;
|
||||||
|
color: #254F1A;
|
||||||
|
padding: 12px 30px;
|
||||||
|
border-radius: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerImage {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerImage img {
|
||||||
|
width: 150px;
|
||||||
|
height: 226px;
|
||||||
|
margin-top: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userInfo {
|
||||||
|
width: 100vw;
|
||||||
|
background-color: white;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
border-radius: 20px 20px 0 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
top: 75vh;
|
||||||
|
transition: top 0.5s ease, padding 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userInfoExpanded {
|
||||||
|
width: 100vw;
|
||||||
|
background-color: white;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
border-radius: 20px 20px 0 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
top: 15vh;
|
||||||
|
transition: top 0.5s ease, padding 0.5s ease;
|
||||||
|
}
|
||||||
|
.userInfoExpandButton {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #d1ecdf;
|
||||||
|
height: 30px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ItemContainer {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Item {
|
||||||
|
background-color: #fff2a3;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.transactionContainer {
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction {
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.rectangle {
|
||||||
|
height: 150px; /* Height of each rectangle */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 10px; /* Rounded corners */
|
||||||
|
font-size: 24px;
|
||||||
|
background-color: rgb(114, 114, 114);
|
||||||
|
}
|
||||||
137
src/pages/Login.js
Normal file
137
src/pages/Login.js
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import styles from './Login.module.css'; // Import the module.css file
|
||||||
|
import { loginUser } from "../helpers/userHelpers";
|
||||||
|
import { ThreeDots } from "react-loader-spinner";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const LinktreePage = ({ user, setModal }) => {
|
||||||
|
const [inputtingPassword, setInputtingPassword] = useState(false);
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
try {
|
||||||
|
setError(false);
|
||||||
|
setLoading(true);
|
||||||
|
const response = await loginUser(username, password);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
localStorage.setItem('auth', response.token);
|
||||||
|
|
||||||
|
if (response.cafeId !== null) {
|
||||||
|
window.location.href = response.cafeId;
|
||||||
|
} else {
|
||||||
|
let destination = '/';
|
||||||
|
window.location.href = destination;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError(true); // Trigger error state in the button
|
||||||
|
console.error('Login failed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError(true);
|
||||||
|
console.error('Error occurred while logging in:', error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false); // Ensure loading state is cleared
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.linktreePage}>
|
||||||
|
|
||||||
|
<div className={styles.dashboardContainer}>
|
||||||
|
<div className={styles.mainHeading}>
|
||||||
|
COBA KEDAIMASTER
|
||||||
|
<div className={styles.swipeContainer}>
|
||||||
|
<div className={styles.swipeContent}>
|
||||||
|
<div className={styles.swipeItem}>pemesanan langsung dari meja</div>
|
||||||
|
<div className={styles.swipeItem}>pengelolaan pesanan dan keuangan</div>
|
||||||
|
<div className={styles.swipeItem}>tentukan suasana musik</div>
|
||||||
|
<div className={styles.swipeItem}>pengelolaan stok dan manajemen</div>
|
||||||
|
<div className={styles.swipeItem}>jangan pernah ragukan pelanggan</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
diskon 0%
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.subHeading}>
|
||||||
|
Solusi berbasis web untuk memudahkan pengelolaan kedai, dengan fitur yang mempermudah pemilik, kasir, dan tamu berinteraksi.
|
||||||
|
</div>
|
||||||
|
<div className={styles.LoginForm}>
|
||||||
|
<div className={`${styles.FormUsername} ${inputtingPassword ? styles.animateForm : styles.reverseForm}`}>
|
||||||
|
<label htmlFor="username" className={styles.usernameLabel}>
|
||||||
|
---- masuk -------------------------------
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
placeholder="username"
|
||||||
|
maxLength="30"
|
||||||
|
className={!error ? styles.usernameInput : styles.usernameInputError}
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={() => setInputtingPassword(true)} className={styles.claimButton}>
|
||||||
|
<span>➜</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`${styles.FormPassword} ${inputtingPassword ? styles.animateForm : styles.reverseForm}`}>
|
||||||
|
<span>
|
||||||
|
<label onClick={() => setInputtingPassword(false)} htmlFor="password" className={styles.usernameLabel}>
|
||||||
|
<--- <-- kembali
|
||||||
|
</label><label htmlFor="password" className={styles.usernameLabel}>
|
||||||
|
------
|
||||||
|
</label><label onClick={() => setModal('reset-password', { username: username })} htmlFor="password" className={styles.usernameLabel}>
|
||||||
|
lupa password? -
|
||||||
|
</label></span>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
placeholder="password"
|
||||||
|
type="password"
|
||||||
|
maxLength="30"
|
||||||
|
className={!error ? styles.usernameInput : styles.usernameInputError}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleLogin}
|
||||||
|
className={`${styles.claimButton} ${loading ? styles.loading : ''}`}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<span>{loading ? 'Loading...' : 'Masuk'}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<div className={styles.footerLinks}>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee/discover/trending"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Pelajari lebih lanjut
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Tentang kedaimaster.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LinktreePage;
|
||||||
320
src/pages/Login.module.css
Normal file
320
src/pages/Login.module.css
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
/* General container */
|
||||||
|
.linktreePage {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgb(210, 232, 35);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboardLine {
|
||||||
|
position: fixed;
|
||||||
|
left: 0px;
|
||||||
|
height: 100vh;
|
||||||
|
width: 30px;
|
||||||
|
border-right: 1px solid black;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboardContainer {
|
||||||
|
background-color: rgb(210, 232, 35);
|
||||||
|
z-index: 6;
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Heading */
|
||||||
|
.mainHeading {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: rgb(37, 79, 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeContainer {
|
||||||
|
height: 75px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
color: rgb(59 130 246);
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeContent {
|
||||||
|
animation: swipeUp 12s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipeItem {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 75px;
|
||||||
|
max-width: 300px;
|
||||||
|
text-wrap: balance;
|
||||||
|
line-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Swipe Animation */
|
||||||
|
@keyframes swipeUp {
|
||||||
|
0%, 15% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
20%, 35% {
|
||||||
|
transform: translateY(-20%);
|
||||||
|
}
|
||||||
|
40%, 55% {
|
||||||
|
transform: translateY(-40%);
|
||||||
|
}
|
||||||
|
60%, 75% {
|
||||||
|
transform: translateY(-60%);
|
||||||
|
}
|
||||||
|
80%, 95% {
|
||||||
|
transform: translateY(-80%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sub Heading */
|
||||||
|
.subHeading {
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'poppins';
|
||||||
|
color: black;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.LoginForm {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
height: 148px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
.FormUsername {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: absolute;
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormUsername.animateForm {
|
||||||
|
animation: FormUsernameProgress 0.5s forwards; /* Apply the animation when inputtingPassword is true */
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormUsername.reverseForm {
|
||||||
|
animation: FormUsernameReverse 0.5s forwards; /* Reverse animation when inputtingPassword is false */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes FormUsernameProgress {
|
||||||
|
0% {
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: -100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes FormUsernameReverse {
|
||||||
|
0% {
|
||||||
|
left: -100vw;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormPassword {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
position: absolute;
|
||||||
|
left: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormPassword.animateForm {
|
||||||
|
animation: FormPasswordProgress 0.5s forwards; /* Apply the animation when inputtingPassword is true */
|
||||||
|
}
|
||||||
|
|
||||||
|
.FormPassword.reverseForm {
|
||||||
|
animation: FormPasswordReverse 0.5s forwards; /* Reverse animation when inputtingPassword is false */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes FormPasswordProgress {
|
||||||
|
0% {
|
||||||
|
left: 100vw;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes FormPasswordReverse {
|
||||||
|
0% {
|
||||||
|
left: 0vw;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.usernameLabel {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #444;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usernameInput {
|
||||||
|
width: 250px;
|
||||||
|
height: 55px;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usernameInputError {
|
||||||
|
width: 250px;
|
||||||
|
height: 55px;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 2px solid red; /* Red border when error is true */
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
/* Apply keyframe animation for border color transition */
|
||||||
|
animation: borderTransition 2s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyframe animation for border color transition */
|
||||||
|
@keyframes borderTransition {
|
||||||
|
0% {
|
||||||
|
border-color: red; /* Initial red border */
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
border-color: transparent; /* Transition to transparent */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claimButton {
|
||||||
|
width: 200px;
|
||||||
|
height: 45px;
|
||||||
|
background-color: #254F1A;
|
||||||
|
color: #D2E823;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 30px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.claimButton span {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerLinks {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerLink {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #254F1A;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signupButton {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #254F1A;
|
||||||
|
color: #254F1A;
|
||||||
|
padding: 12px 30px;
|
||||||
|
border-radius: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerImage {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerImage img {
|
||||||
|
width: 150px;
|
||||||
|
height: 226px;
|
||||||
|
margin-top: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userInfo {
|
||||||
|
width: 100vw;
|
||||||
|
background-color: white;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
border-radius: 20px 20px 0 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
top: 75vh;
|
||||||
|
transition: top 0.5s ease, padding 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userInfoExpanded {
|
||||||
|
width: 100vw;
|
||||||
|
background-color: white;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
border-radius: 20px 20px 0 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
top: 15vh;
|
||||||
|
transition: top 0.5s ease, padding 0.5s ease;
|
||||||
|
}
|
||||||
|
.userInfoExpandButton {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #d1ecdf;
|
||||||
|
height: 30px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ItemContainer {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Item {
|
||||||
|
background-color: #fff2a3;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.transactionContainer {
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction {
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/* src/Login.css */
|
|
||||||
.login-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
background-color: #ffffff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form h2 {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: #555555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #cccccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #ffffff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
|
||||||
import "./LoginPage.css";
|
|
||||||
import RouletteWheel from "../components/RouletteWheel";
|
|
||||||
import { loginUser, signUpUser } from "../helpers/userHelpers";
|
|
||||||
|
|
||||||
const LoginPage = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const location = useLocation();
|
|
||||||
const searchParams = new URLSearchParams(location.search);
|
|
||||||
const next = searchParams.get("next");
|
|
||||||
const table = searchParams.get("table");
|
|
||||||
|
|
||||||
const handleLogin = async (
|
|
||||||
email,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
setLoading,
|
|
||||||
setError
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const response = await loginUser(username, password);
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
localStorage.setItem("auth", response.token);
|
|
||||||
|
|
||||||
if (response.cafeId !== null) {
|
|
||||||
window.location.href = response.cafeId;
|
|
||||||
} else {
|
|
||||||
let destination = "/";
|
|
||||||
if (table && !next) {
|
|
||||||
console.error('Parameter "table" requires "next" to be present.');
|
|
||||||
navigate("/");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (next) {
|
|
||||||
destination = `/${next}`;
|
|
||||||
if (table) destination += `?table=${table}`;
|
|
||||||
}
|
|
||||||
window.location.href = destination;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setError(true); // Trigger error state in the button
|
|
||||||
console.error("Login failed");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setError(true);
|
|
||||||
console.error("Error occurred while logging in:", error.message);
|
|
||||||
} finally {
|
|
||||||
setLoading(false); // Ensure loading state is cleared
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleSignUp = async (
|
|
||||||
email,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
setLoading,
|
|
||||||
setError
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const response = await signUpUser(email, username, password);
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
localStorage.setItem("auth", response.token);
|
|
||||||
|
|
||||||
let destination = "/";
|
|
||||||
|
|
||||||
window.location.href = destination;
|
|
||||||
} else {
|
|
||||||
setError(true); // Trigger error state in the button
|
|
||||||
console.error("Login failed");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setError(true);
|
|
||||||
console.error("Error occurred while logging in:", error.message);
|
|
||||||
} finally {
|
|
||||||
setLoading(false); // Ensure loading state is cleared
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="login-container">
|
|
||||||
<RouletteWheel onSignIn={handleLogin} onSignUp={handleSignUp} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoginPage;
|
|
||||||
70
src/pages/ResetPassword.js
Normal file
70
src/pages/ResetPassword.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styles from './Join.module.css'; // Import the module.css file
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
const LinktreePage = ({ setModal }) => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const username = searchParams.get("username") || "";
|
||||||
|
return (
|
||||||
|
<div className={styles.linktreePage}>
|
||||||
|
|
||||||
|
<div className={styles.dashboardContainer}>
|
||||||
|
{/* Main Heading */}
|
||||||
|
<div className={styles.mainHeading}>
|
||||||
|
Lupa password
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sub Heading */}
|
||||||
|
<div className={styles.subHeading}>
|
||||||
|
Masukkan email atau nomor whatsapp, kami akan memberimu kode akses
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Form Section */}
|
||||||
|
<form className={styles.linktreeForm}>
|
||||||
|
<label htmlFor="username" className={styles.usernameLabel}>--------- username atau email -------------</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
placeholder=""
|
||||||
|
maxLength="30"
|
||||||
|
className={styles.usernameInput}
|
||||||
|
value={username}
|
||||||
|
/>
|
||||||
|
<button type="submit" className={styles.claimButton}>
|
||||||
|
<span>➜</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Footer Links */}
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<div className={styles.footerLinks}>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee/discover/trending"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Pelajari lebih lanjut
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://linktr.ee"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.footerLink}
|
||||||
|
>
|
||||||
|
Tentang kedaimaster.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className={styles.footerImage}>
|
||||||
|
<img
|
||||||
|
src="./laporan.png"
|
||||||
|
alt="Linktree visual"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LinktreePage;
|
||||||
Reference in New Issue
Block a user