From 2a696f96c1eb2a2ab703d5557faca5f98e644fda Mon Sep 17 00:00:00 2001 From: Olly Hearn Date: Mon, 22 Apr 2024 00:48:39 +0300 Subject: [PATCH] join queue --- frontend/app/src/components/AuthModal.tsx | 9 +- .../components/queue/ApproveQueueJoinCard.tsx | 58 ++++++++++ .../src/components/queue/JoinQueueCard.tsx | 47 ++++++++ .../components/queue/JoinQueueCard.tsx.old | 104 ++++++++++++++++++ frontend/app/src/pages/AppRoutes.tsx | 4 + .../app/src/pages/ApproveQueueJoinPage.tsx | 19 ++++ frontend/app/src/pages/JoinQueuePage.tsx | 13 +++ frontend/app/src/pages/MainPage.tsx | 5 +- frontend/app/src/slice/QueueApi.ts | 3 + 9 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 frontend/app/src/components/queue/ApproveQueueJoinCard.tsx create mode 100644 frontend/app/src/components/queue/JoinQueueCard.tsx create mode 100644 frontend/app/src/components/queue/JoinQueueCard.tsx.old create mode 100644 frontend/app/src/pages/ApproveQueueJoinPage.tsx create mode 100644 frontend/app/src/pages/JoinQueuePage.tsx diff --git a/frontend/app/src/components/AuthModal.tsx b/frontend/app/src/components/AuthModal.tsx index b91c59b..7d9f52d 100644 --- a/frontend/app/src/components/AuthModal.tsx +++ b/frontend/app/src/components/AuthModal.tsx @@ -2,7 +2,6 @@ import { Button, Carousel, Form, - Image, Input, Menu, MenuProps, @@ -11,11 +10,10 @@ import { } from "antd"; import { KeyOutlined, - LoadingOutlined, ReloadOutlined, UserAddOutlined, } from "@ant-design/icons"; -import React, { useContext, useEffect, useRef, useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { TokenResponse, useGetClientQuery, @@ -29,6 +27,7 @@ import tr from "../config/translation"; import { useNavigate } from "react-router-dom"; import { v4 as uuidv4 } from "uuid"; import { baseUrl } from "../config/baseUrl"; +import { CarouselRef } from "antd/es/carousel"; const AuthModal = (props: { open: boolean; @@ -37,7 +36,7 @@ const AuthModal = (props: { }) => { const navigate = useNavigate(); const messageApi = useContext(MessageContext); - const carousel = useRef(); + const carousel = React.createRef(); const [loginForm] = Form.useForm(); const [registerForm] = Form.useForm(); @@ -82,7 +81,7 @@ const AuthModal = (props: { const [current, setCurrent] = useState("login"); useEffect(() => { - if (carousel?.current !== undefined) { + if (carousel && carousel.current && carousel?.current !== undefined) { carousel.current.goTo(["login", "register"].indexOf(current)); } }, [current]); diff --git a/frontend/app/src/components/queue/ApproveQueueJoinCard.tsx b/frontend/app/src/components/queue/ApproveQueueJoinCard.tsx new file mode 100644 index 0000000..fbb136a --- /dev/null +++ b/frontend/app/src/components/queue/ApproveQueueJoinCard.tsx @@ -0,0 +1,58 @@ +import React, { useState } from "react"; +import "../styles.css"; +import { Button, Divider, Input, Spin } from "antd"; +import { + ArrowLeftOutlined, + CameraOutlined, + FileTextOutlined, + PlusOutlined, + UserOutlined, +} from "@ant-design/icons"; +import Title from "antd/es/typography/Title"; +import tr from "../../config/translation"; +import { useNavigate } from "react-router-dom"; +import { useGetQueueDetailQuery } from "../../slice/QueueApi"; + +const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => { + const navigate = useNavigate(); + const { data, isFetching, isError } = useGetQueueDetailQuery(props.id, { + skip: !props.id, + }); + + return ( +
+ + {isError ? ( + <> + {tr("Queue not found!")} + + + ) : ( +
+ + {data?.name} + +

+ + {" "} + {data?.description} +

+

+ + {" "} + {data?.participants?.remaining} / {data?.participants?.total} +

+ +
+ )} +
+
+ ); +}; +export default ApproveQueueJoinCard; diff --git a/frontend/app/src/components/queue/JoinQueueCard.tsx b/frontend/app/src/components/queue/JoinQueueCard.tsx new file mode 100644 index 0000000..99b1780 --- /dev/null +++ b/frontend/app/src/components/queue/JoinQueueCard.tsx @@ -0,0 +1,47 @@ +import React, { useState } from "react"; +import "../styles.css"; +import { Button, Divider, Input } from "antd"; +import { CameraOutlined } from "@ant-design/icons"; +import Title from "antd/es/typography/Title"; +import tr from "../../config/translation"; +import { useNavigate } from "react-router-dom"; + +const getLink = (v: string) => { + if (v.includes("v")) { + return "/queue/join/" + v.split("/").slice(-1).pop(); + } + return `/queue/join/${v}`; +}; + +const JoinQueueCard = (): JSX.Element => { + const navigate = useNavigate(); + const [value, setValue] = useState(""); + const processLink = () => { + navigate(getLink(value)); + }; + return ( +
+ + {tr("QR-code scanner in development")} + {tr("OR")} + {tr("Join queue by link or id")} +
+ setValue(e.target.value)} + /> + +
+
+ ); +}; +export default JoinQueueCard; diff --git a/frontend/app/src/components/queue/JoinQueueCard.tsx.old b/frontend/app/src/components/queue/JoinQueueCard.tsx.old new file mode 100644 index 0000000..a55c943 --- /dev/null +++ b/frontend/app/src/components/queue/JoinQueueCard.tsx.old @@ -0,0 +1,104 @@ +import React, { useEffect, useRef, useState } from "react"; +import { useGetQueueDetailQuery } from "../../slice/QueueApi"; +import "../styles.css"; +import { Button, Divider, Spin } from "antd"; +import { + FileTextOutlined, + LoadingOutlined, + UserOutlined, +} from "@ant-design/icons"; +import Title from "antd/es/typography/Title"; +import tr from "../../config/translation"; +import { Link, useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { StorePrototype } from "../../config/store"; +import QrScanner from "qr-scanner"; + +export function useUserMedia() { + const requestedMedia = { + audio: false, + video: { facingMode: "environment" }, + }; + const [mediaStream, setMediaStream] = useState(); + + useEffect(() => { + async function enableStream() { + try { + const stream = await navigator.mediaDevices.getUserMedia( + requestedMedia + ); + setMediaStream(stream); + } catch (err) { + // Removed for brevity + } + } + + if (!mediaStream) { + enableStream(); + } else { + return function cleanup() { + mediaStream.getTracks().forEach((track) => { + track.stop(); + }); + }; + } + }, [mediaStream, requestedMedia]); + + return mediaStream; +} + +const JoinQueueCard = (): JSX.Element => { + const clientId = useSelector((state: StorePrototype) => state.auth.clientId); + const mediaStream = useUserMedia(); + + // const permission = navigator.permissions.query({ name: "camera" }); + + const videoElem = React.createRef(); + + if (mediaStream && videoElem.current && !videoElem.current.srcObject) { + videoElem.current.srcObject = mediaStream; + } + + const [scanner, setScanner] = useState(); + const [scannerStarted, setScannerStarted] = useState(false); + if (videoElem.current) { + const newScanner = new QrScanner( + videoElem.current, + (result) => console.log("decoded qr code:", result), + { + /* your options or returnDetailedScanResult: true if you're not specifying any other options */ + } + ); + setScanner(newScanner); + } + if (scanner) { + scanner.start(); + } + + // useEffect(() => { + // if (videoElem.current) { + // const newScanner = new QrScanner( + // videoElem.current, + // (result) => console.log("decoded qr code:", result), + // { + // /* your options or returnDetailedScanResult: true if you're not specifying any other options */ + // } + // ); + // setScanner(newScanner); + // newScanner.start(); + // } + // }, [videoElem]); + // scanner.start(); + + // const qrScanner = + // videoElem.current && + + return ( +
+
+ ); +}; +export default JoinQueueCard; diff --git a/frontend/app/src/pages/AppRoutes.tsx b/frontend/app/src/pages/AppRoutes.tsx index 48d5cfc..28e7ad5 100644 --- a/frontend/app/src/pages/AppRoutes.tsx +++ b/frontend/app/src/pages/AppRoutes.tsx @@ -15,6 +15,8 @@ import NewQueuePage from "./NewQueuePage"; import NewsPage from "./NewsPage"; import CreateNewsPage from "./CreateNewsPage"; import QueueCard from "../components/queue/QueueCard"; +import JoinQueuePage from "./JoinQueuePage"; +import ApproveQueueJoinPage from "./ApproveQueueJoinPage"; const AppRoutes = ({ children }: { children: ReactNode }) => { store.dispatch(getLocalToken()); @@ -29,6 +31,8 @@ const AppRoutes = ({ children }: { children: ReactNode }) => { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/frontend/app/src/pages/ApproveQueueJoinPage.tsx b/frontend/app/src/pages/ApproveQueueJoinPage.tsx new file mode 100644 index 0000000..54ca2e7 --- /dev/null +++ b/frontend/app/src/pages/ApproveQueueJoinPage.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import "./styles.css"; +import { useSelector } from "react-redux"; +import { StorePrototype } from "../config/store"; +import NotFoundPage from "./NotFoundPage"; +import ApproveQueueJoinCard from "../components/queue/ApproveQueueJoinCard"; +import { useParams } from "react-router-dom"; + +const ApproveQueueJoinPage = () => { + const clientId = useSelector((state: StorePrototype) => state.auth.clientId); + const { queueId } = useParams(); + return clientId && queueId ? ( + + ) : ( + + ); +}; + +export default ApproveQueueJoinPage; diff --git a/frontend/app/src/pages/JoinQueuePage.tsx b/frontend/app/src/pages/JoinQueuePage.tsx new file mode 100644 index 0000000..dc2508d --- /dev/null +++ b/frontend/app/src/pages/JoinQueuePage.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import "./styles.css"; +import { useSelector } from "react-redux"; +import { StorePrototype } from "../config/store"; +import NotFoundPage from "./NotFoundPage"; +import JoinQueueCard from "../components/queue/JoinQueueCard"; + +const JoinQueuePage = () => { + const clientId = useSelector((state: StorePrototype) => state.auth.clientId); + return clientId ? : ; +}; + +export default JoinQueuePage; diff --git a/frontend/app/src/pages/MainPage.tsx b/frontend/app/src/pages/MainPage.tsx index ed99b62..8dca728 100644 --- a/frontend/app/src/pages/MainPage.tsx +++ b/frontend/app/src/pages/MainPage.tsx @@ -5,6 +5,7 @@ import { Alert, Button } from "antd"; import tr from "../config/translation"; import { isMobile } from "react-device-detect"; import { WarningOutlined } from "@ant-design/icons"; +import { Link } from "react-router-dom"; const MainPage = () => { return ( @@ -23,7 +24,9 @@ const MainPage = () => { style={{ marginBottom: "1rem" }} />
- + + + {!( isMobile && window.screen.orientation.type === "portrait-primary" ) &&
} diff --git a/frontend/app/src/slice/QueueApi.ts b/frontend/app/src/slice/QueueApi.ts index 8a9a77e..5520764 100644 --- a/frontend/app/src/slice/QueueApi.ts +++ b/frontend/app/src/slice/QueueApi.ts @@ -36,6 +36,9 @@ export const QueueApi = createApi({ getQueueDetail: builder.query({ query: (queueId: string | undefined) => `/${queueId}`, }), + joinQueue: builder.mutation({ + query: (queueId: string) => `/${queueId}/join`, + }), createQueue: builder.mutation({ query: (data: CreateQueueRequest) => ({ url: "/",