join queue

This commit is contained in:
2024-04-22 00:48:39 +03:00
parent 227f1c5782
commit 2a696f96c1
9 changed files with 256 additions and 6 deletions

View File

@ -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<CarouselRef>();
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]);

View File

@ -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 (
<div className="card">
<Spin spinning={isFetching}>
{isError ? (
<>
<Title level={3}>{tr("Queue not found!")}</Title>
<Button
icon={<ArrowLeftOutlined />}
type="primary"
onClick={() => navigate("/queue/join")}
>
{tr("Go back")}
</Button>
</>
) : (
<div className="queue-info">
<Title level={3} style={{ textAlign: "left" }}>
{data?.name}
</Title>
<p>
<FileTextOutlined />
{" "}
{data?.description}
</p>
<p>
<UserOutlined />
{" "}
{data?.participants?.remaining} / {data?.participants?.total}
</p>
<Button icon={<PlusOutlined />}>{tr("Join")}</Button>
</div>
)}
</Spin>
</div>
);
};
export default ApproveQueueJoinCard;

View File

@ -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 (
<div className="card">
<CameraOutlined />
<Title level={3}>{tr("QR-code scanner in development")}</Title>
<Divider>{tr("OR")}</Divider>
<Title level={3}>{tr("Join queue by link or id")}</Title>
<div
style={{
width: "100%",
display: "flex",
flexFlow: "row",
}}
>
<Input
style={{ width: "100rem" }}
placeholder={tr("Queue link or id")}
onChange={(e) => setValue(e.target.value)}
/>
<Button type="primary" onClick={() => processLink()}>
{tr("Join")}
</Button>
</div>
</div>
);
};
export default JoinQueueCard;

View File

@ -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<MediaStream>();
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<HTMLVideoElement>();
if (mediaStream && videoElem.current && !videoElem.current.srcObject) {
videoElem.current.srcObject = mediaStream;
}
const [scanner, setScanner] = useState<QrScanner>();
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 (
<div className="card">
<video ref={videoElem} autoPlay playsInline muted />
<Divider>{tr("OR")}</Divider>
</div>
);
};
export default JoinQueueCard;

View File

@ -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 }) => {
<Route path="/" element={<MainPage />} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/queue/:queueId" element={<QueueCard />} />
<Route path="/queue/join" element={<JoinQueuePage />} />
<Route path="/queue/join/:queueId" element={<ApproveQueueJoinPage />} />
<Route path="/dashboard/new" element={<NewQueuePage />} />
<Route path="/news" element={<NewsPage />} />
<Route path="/news/new" element={<CreateNewsPage />} />

View File

@ -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 ? (
<ApproveQueueJoinCard id={queueId} />
) : (
<NotFoundPage />
);
};
export default ApproveQueueJoinPage;

View File

@ -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 ? <JoinQueueCard /> : <NotFoundPage />;
};
export default JoinQueuePage;

View File

@ -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" }}
/>
<div className="button-box">
<Button size="large">{tr("Join a queue")}</Button>
<Link to="/queue/join">
<Button size="large">{tr("Join a queue")}</Button>
</Link>
{!(
isMobile && window.screen.orientation.type === "portrait-primary"
) && <div style={{ width: "3rem" }} />}

View File

@ -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: "/",