join queue
This commit is contained in:
@ -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]);
|
||||
|
||||
58
frontend/app/src/components/queue/ApproveQueueJoinCard.tsx
Normal file
58
frontend/app/src/components/queue/ApproveQueueJoinCard.tsx
Normal 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;
|
||||
47
frontend/app/src/components/queue/JoinQueueCard.tsx
Normal file
47
frontend/app/src/components/queue/JoinQueueCard.tsx
Normal 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;
|
||||
104
frontend/app/src/components/queue/JoinQueueCard.tsx.old
Normal file
104
frontend/app/src/components/queue/JoinQueueCard.tsx.old
Normal 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;
|
||||
@ -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 />} />
|
||||
|
||||
19
frontend/app/src/pages/ApproveQueueJoinPage.tsx
Normal file
19
frontend/app/src/pages/ApproveQueueJoinPage.tsx
Normal 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;
|
||||
13
frontend/app/src/pages/JoinQueuePage.tsx
Normal file
13
frontend/app/src/pages/JoinQueuePage.tsx
Normal 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;
|
||||
@ -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" }} />}
|
||||
|
||||
@ -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: "/",
|
||||
|
||||
Reference in New Issue
Block a user