join queue
This commit is contained in:
@ -2,7 +2,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Carousel,
|
Carousel,
|
||||||
Form,
|
Form,
|
||||||
Image,
|
|
||||||
Input,
|
Input,
|
||||||
Menu,
|
Menu,
|
||||||
MenuProps,
|
MenuProps,
|
||||||
@ -11,11 +10,10 @@ import {
|
|||||||
} from "antd";
|
} from "antd";
|
||||||
import {
|
import {
|
||||||
KeyOutlined,
|
KeyOutlined,
|
||||||
LoadingOutlined,
|
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
UserAddOutlined,
|
UserAddOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
TokenResponse,
|
TokenResponse,
|
||||||
useGetClientQuery,
|
useGetClientQuery,
|
||||||
@ -29,6 +27,7 @@ import tr from "../config/translation";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { baseUrl } from "../config/baseUrl";
|
import { baseUrl } from "../config/baseUrl";
|
||||||
|
import { CarouselRef } from "antd/es/carousel";
|
||||||
|
|
||||||
const AuthModal = (props: {
|
const AuthModal = (props: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -37,7 +36,7 @@ const AuthModal = (props: {
|
|||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const messageApi = useContext(MessageContext);
|
const messageApi = useContext(MessageContext);
|
||||||
const carousel = useRef();
|
const carousel = React.createRef<CarouselRef>();
|
||||||
|
|
||||||
const [loginForm] = Form.useForm();
|
const [loginForm] = Form.useForm();
|
||||||
const [registerForm] = Form.useForm();
|
const [registerForm] = Form.useForm();
|
||||||
@ -82,7 +81,7 @@ const AuthModal = (props: {
|
|||||||
|
|
||||||
const [current, setCurrent] = useState("login");
|
const [current, setCurrent] = useState("login");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (carousel?.current !== undefined) {
|
if (carousel && carousel.current && carousel?.current !== undefined) {
|
||||||
carousel.current.goTo(["login", "register"].indexOf(current));
|
carousel.current.goTo(["login", "register"].indexOf(current));
|
||||||
}
|
}
|
||||||
}, [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 NewsPage from "./NewsPage";
|
||||||
import CreateNewsPage from "./CreateNewsPage";
|
import CreateNewsPage from "./CreateNewsPage";
|
||||||
import QueueCard from "../components/queue/QueueCard";
|
import QueueCard from "../components/queue/QueueCard";
|
||||||
|
import JoinQueuePage from "./JoinQueuePage";
|
||||||
|
import ApproveQueueJoinPage from "./ApproveQueueJoinPage";
|
||||||
|
|
||||||
const AppRoutes = ({ children }: { children: ReactNode }) => {
|
const AppRoutes = ({ children }: { children: ReactNode }) => {
|
||||||
store.dispatch(getLocalToken());
|
store.dispatch(getLocalToken());
|
||||||
@ -29,6 +31,8 @@ const AppRoutes = ({ children }: { children: ReactNode }) => {
|
|||||||
<Route path="/" element={<MainPage />} />
|
<Route path="/" element={<MainPage />} />
|
||||||
<Route path="/dashboard" element={<DashboardPage />} />
|
<Route path="/dashboard" element={<DashboardPage />} />
|
||||||
<Route path="/queue/:queueId" element={<QueueCard />} />
|
<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="/dashboard/new" element={<NewQueuePage />} />
|
||||||
<Route path="/news" element={<NewsPage />} />
|
<Route path="/news" element={<NewsPage />} />
|
||||||
<Route path="/news/new" element={<CreateNewsPage />} />
|
<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 tr from "../config/translation";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import { WarningOutlined } from "@ant-design/icons";
|
import { WarningOutlined } from "@ant-design/icons";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
const MainPage = () => {
|
const MainPage = () => {
|
||||||
return (
|
return (
|
||||||
@ -23,7 +24,9 @@ const MainPage = () => {
|
|||||||
style={{ marginBottom: "1rem" }}
|
style={{ marginBottom: "1rem" }}
|
||||||
/>
|
/>
|
||||||
<div className="button-box">
|
<div className="button-box">
|
||||||
|
<Link to="/queue/join">
|
||||||
<Button size="large">{tr("Join a queue")}</Button>
|
<Button size="large">{tr("Join a queue")}</Button>
|
||||||
|
</Link>
|
||||||
{!(
|
{!(
|
||||||
isMobile && window.screen.orientation.type === "portrait-primary"
|
isMobile && window.screen.orientation.type === "portrait-primary"
|
||||||
) && <div style={{ width: "3rem" }} />}
|
) && <div style={{ width: "3rem" }} />}
|
||||||
|
|||||||
@ -36,6 +36,9 @@ export const QueueApi = createApi({
|
|||||||
getQueueDetail: builder.query({
|
getQueueDetail: builder.query({
|
||||||
query: (queueId: string | undefined) => `/${queueId}`,
|
query: (queueId: string | undefined) => `/${queueId}`,
|
||||||
}),
|
}),
|
||||||
|
joinQueue: builder.mutation({
|
||||||
|
query: (queueId: string) => `/${queueId}/join`,
|
||||||
|
}),
|
||||||
createQueue: builder.mutation({
|
createQueue: builder.mutation({
|
||||||
query: (data: CreateQueueRequest) => ({
|
query: (data: CreateQueueRequest) => ({
|
||||||
url: "/",
|
url: "/",
|
||||||
|
|||||||
Reference in New Issue
Block a user