participantlist works!
This commit is contained in:
@ -78,7 +78,7 @@ async def read_users_me(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/anon")
|
@router.get("/anon")
|
||||||
async def get_qnon_user(
|
async def get_anon_user(
|
||||||
anon_user: Annotated[schemas.AnonUser, Depends(services.get_anon_user)]
|
anon_user: Annotated[schemas.AnonUser, Depends(services.get_anon_user)]
|
||||||
) -> schemas.AnonUser:
|
) -> schemas.AnonUser:
|
||||||
return anon_user
|
return anon_user
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from uuid import UUID
|
|||||||
import redis
|
import redis
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from ...dependencies import get_db, get_pubsub
|
from ...dependencies import get_db, get_pubsub, get_redis
|
||||||
from ...db import models
|
from ...db import models
|
||||||
|
|
||||||
from ..auth import services as auth_services
|
from ..auth import services as auth_services
|
||||||
@ -43,7 +43,6 @@ def get_detailed_queue(
|
|||||||
db: Annotated[Session, Depends(get_db)],
|
db: Annotated[Session, Depends(get_db)],
|
||||||
) -> schemas.QueueDetail:
|
) -> schemas.QueueDetail:
|
||||||
q = db.query(models.Queue).filter(models.Queue.id == queue_id).first()
|
q = db.query(models.Queue).filter(models.Queue.id == queue_id).first()
|
||||||
print("\n\n", queue_id, "\n\n", flush=True)
|
|
||||||
if q:
|
if q:
|
||||||
return schemas.QueueDetail(
|
return schemas.QueueDetail(
|
||||||
id=q.id,
|
id=q.id,
|
||||||
@ -63,10 +62,11 @@ def get_detailed_queue(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def join_queue(
|
async def join_queue(
|
||||||
queue_id: UUID,
|
queue_id: UUID,
|
||||||
client: Annotated[auth_schemas.AnonUser, Depends(auth_services.get_anon_user)],
|
client: Annotated[auth_schemas.AnonUser, Depends(auth_services.get_anon_user)],
|
||||||
db: Annotated[Session, Depends(get_db)],
|
db: Annotated[Session, Depends(get_db)],
|
||||||
|
r: Annotated[redis.client.Redis, Depends(get_redis)],
|
||||||
) -> schemas.QueueUser:
|
) -> schemas.QueueUser:
|
||||||
q = get_queue_by_id(queue_id, db)
|
q = get_queue_by_id(queue_id, db)
|
||||||
if q:
|
if q:
|
||||||
@ -78,6 +78,7 @@ def join_queue(
|
|||||||
)
|
)
|
||||||
db.add(new_qu)
|
db.add(new_qu)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
await r.publish(str(queue_id), "updated")
|
||||||
return new_qu
|
return new_qu
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
@ -96,9 +97,8 @@ async def set_queue_listener(
|
|||||||
) -> schemas.QueueDetail:
|
) -> schemas.QueueDetail:
|
||||||
await ps.subscribe(str(queue_id))
|
await ps.subscribe(str(queue_id))
|
||||||
async for m in ps.listen():
|
async for m in ps.listen():
|
||||||
print(m, flush=True)
|
|
||||||
if m.get("data", None) == b"updated":
|
if m.get("data", None) == b"updated":
|
||||||
print("UPDATED", flush=True)
|
|
||||||
break
|
break
|
||||||
|
await ps.unsubscribe()
|
||||||
new_queue = get_detailed_queue(queue_id=queue_id, db=db)
|
new_queue = get_detailed_queue(queue_id=queue_id, db=db)
|
||||||
return new_queue
|
return new_queue
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useContext } from "react";
|
||||||
import "../styles.css";
|
import "../styles.css";
|
||||||
import { Button, Divider, Input, Spin } from "antd";
|
import { Button, Spin } from "antd";
|
||||||
import {
|
import {
|
||||||
ArrowLeftOutlined,
|
ArrowLeftOutlined,
|
||||||
CameraOutlined,
|
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
@ -11,13 +10,34 @@ import {
|
|||||||
import Title from "antd/es/typography/Title";
|
import Title from "antd/es/typography/Title";
|
||||||
import tr from "../../config/translation";
|
import tr from "../../config/translation";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useGetQueueDetailQuery } from "../../slice/QueueApi";
|
import {
|
||||||
|
useGetQueueDetailQuery,
|
||||||
|
useJoinQueueMutation,
|
||||||
|
} from "../../slice/QueueApi";
|
||||||
|
import { MessageContext } from "../../App";
|
||||||
|
|
||||||
const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
|
const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, isFetching, isError } = useGetQueueDetailQuery(props.id, {
|
const messageApi = useContext(MessageContext);
|
||||||
skip: !props.id,
|
|
||||||
});
|
const { data, refetch, isFetching, isError } = useGetQueueDetailQuery(
|
||||||
|
props.id,
|
||||||
|
{
|
||||||
|
skip: !props.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const [joinQueue, { isLoading }] = useJoinQueueMutation();
|
||||||
|
|
||||||
|
const onJoinButtonClick = () => {
|
||||||
|
joinQueue(props.id)
|
||||||
|
.unwrap()
|
||||||
|
.then(() =>
|
||||||
|
messageApi.success(tr("Successfully joined queue ") + data.name)
|
||||||
|
)
|
||||||
|
.then(() => navigate(`/queue/${props.id}`))
|
||||||
|
.then(() => refetch())
|
||||||
|
.catch((e) => messageApi.error(tr(e.data.detail)));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
@ -48,7 +68,11 @@ const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
|
|||||||
{" "}
|
{" "}
|
||||||
{data?.participants?.remaining} / {data?.participants?.total}
|
{data?.participants?.remaining} / {data?.participants?.total}
|
||||||
</p>
|
</p>
|
||||||
<Button icon={<PlusOutlined />}>{tr("Join")}</Button>
|
<Spin spinning={isLoading}>
|
||||||
|
<Button icon={<PlusOutlined />} onClick={onJoinButtonClick}>
|
||||||
|
{tr("Join")}
|
||||||
|
</Button>
|
||||||
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Spin>
|
</Spin>
|
||||||
|
|||||||
@ -13,11 +13,15 @@ import tr from "../../config/translation";
|
|||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { StorePrototype } from "../../config/store";
|
import { StorePrototype } from "../../config/store";
|
||||||
|
import AnonUserCard from "../user/AnonUserCard";
|
||||||
|
|
||||||
const QueueCard = (): JSX.Element => {
|
const QueueCard = (): JSX.Element => {
|
||||||
const user = useSelector((state: StorePrototype) => state.auth.user);
|
const user = useSelector((state: StorePrototype) => state.auth.user);
|
||||||
const { queueId } = useParams();
|
const { queueId } = useParams();
|
||||||
const { data, isFetching } = useGetQueueDetailQuery(queueId);
|
const { data, isFetching } = useGetQueueDetailQuery(queueId, {
|
||||||
|
skip: !queueId,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<Spin
|
<Spin
|
||||||
@ -39,6 +43,14 @@ const QueueCard = (): JSX.Element => {
|
|||||||
{data?.participants?.remaining} / {data?.participants?.total}
|
{data?.participants?.remaining} / {data?.participants?.total}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<Title level={3} style={{ textAlign: "left" }}>
|
||||||
|
{tr("Queue participants")}
|
||||||
|
</Title>
|
||||||
|
{data?.participants.users_list.map((v) => {
|
||||||
|
return <AnonUserCard key={v.id} user={v} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</Spin>
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -101,3 +101,25 @@
|
|||||||
.queue-info > * {
|
.queue-info > * {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.anon-circle {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anon-card {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
align-items: center;
|
||||||
|
animation: 0.3s ease-out 0s 1 cardPopup;
|
||||||
|
background: #001d39;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
margin-left: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 2px solid #00d8a4;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,45 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { AnonUser } from "../../slice/AuthApi";
|
||||||
|
import "../styles.css";
|
||||||
|
import tr from "../../config/translation";
|
||||||
|
import Title from "antd/es/typography/Title";
|
||||||
|
import Paragraph from "antd/es/typography/Paragraph";
|
||||||
|
|
||||||
const UUIDToColor = (uuid: string): string => {};
|
const UUIDToColor = (uuid: string): string => {
|
||||||
|
return (
|
||||||
const AnonUserCard = (): JSX.Element => {
|
"#" +
|
||||||
return;
|
uuid.split("-").reduce((store: string, v: string) => {
|
||||||
|
return store + v.substring(0, 1);
|
||||||
|
}, "") +
|
||||||
|
uuid.substring(uuid.length - 1)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getProfileText = (name: string): string => {
|
||||||
|
return name.substring(0, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AnonUserCard = (props: {
|
||||||
|
user: AnonUser;
|
||||||
|
backlight?: "self" | "active" | undefined;
|
||||||
|
}): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div className="anon-card">
|
||||||
|
<div
|
||||||
|
className="anon-circle"
|
||||||
|
style={{ background: UUIDToColor(props.user.id) }}
|
||||||
|
>
|
||||||
|
{props.user.name
|
||||||
|
? getProfileText(props.user.name)
|
||||||
|
: props.user.id.substring(0, 2)}
|
||||||
|
</div>
|
||||||
|
<p color="white" style={{ marginLeft: "10px" }}>
|
||||||
|
{props.user.name
|
||||||
|
? props.user.name
|
||||||
|
: tr("Anonymous") + " #" + props.user.id.substring(0, 4)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnonUserCard;
|
||||||
|
|||||||
@ -24,6 +24,11 @@ export type TokenResponse = {
|
|||||||
token_type: string;
|
token_type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AnonUser = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const AuthApi = createApi({
|
export const AuthApi = createApi({
|
||||||
reducerPath: "AuthApi",
|
reducerPath: "AuthApi",
|
||||||
baseQuery: fetchBaseQuery({
|
baseQuery: fetchBaseQuery({
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||||
import { baseUrl } from "../config/baseUrl";
|
import { baseUrl } from "../config/baseUrl";
|
||||||
import { RootState } from "../config/store";
|
import { RootState } from "../config/store";
|
||||||
|
import { AnonUser } from "./AuthApi";
|
||||||
|
|
||||||
export type CreateQueueRequest = {
|
export type CreateQueueRequest = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -13,6 +14,17 @@ export type Queue = {
|
|||||||
description: string | null;
|
description: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type QueueDetail = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
participants: {
|
||||||
|
total: number;
|
||||||
|
remaining: number;
|
||||||
|
users_list: [AnonUser];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const QueueApi = createApi({
|
export const QueueApi = createApi({
|
||||||
reducerPath: "QueueApi",
|
reducerPath: "QueueApi",
|
||||||
baseQuery: fetchBaseQuery({
|
baseQuery: fetchBaseQuery({
|
||||||
@ -30,14 +42,14 @@ export const QueueApi = createApi({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getQueues: builder.query({
|
getQueues: builder.query<[Queue], undefined>({
|
||||||
query: () => "/",
|
query: () => "/",
|
||||||
}),
|
}),
|
||||||
getQueueDetail: builder.query({
|
getQueueDetail: builder.query<QueueDetail, string | undefined>({
|
||||||
query: (queueId: string | undefined) => `/${queueId}`,
|
query: (queueId: string | undefined) => `/${queueId}`,
|
||||||
}),
|
}),
|
||||||
joinQueue: builder.mutation({
|
joinQueue: builder.mutation({
|
||||||
query: (queueId: string) => `/${queueId}/join`,
|
query: (queueId: string) => ({ url: `/${queueId}/join`, method: "POST" }),
|
||||||
}),
|
}),
|
||||||
createQueue: builder.mutation({
|
createQueue: builder.mutation({
|
||||||
query: (data: CreateQueueRequest) => ({
|
query: (data: CreateQueueRequest) => ({
|
||||||
@ -52,5 +64,6 @@ export const QueueApi = createApi({
|
|||||||
export const {
|
export const {
|
||||||
useGetQueuesQuery,
|
useGetQueuesQuery,
|
||||||
useGetQueueDetailQuery,
|
useGetQueueDetailQuery,
|
||||||
|
useJoinQueueMutation,
|
||||||
useCreateQueueMutation,
|
useCreateQueueMutation,
|
||||||
} = QueueApi;
|
} = QueueApi;
|
||||||
|
|||||||
Reference in New Issue
Block a user