upd
This commit is contained in:
+14
@@ -1,4 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
⠟⢦⡀
|
||||
⢷⡄⠈⡓⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⠤⠂⢹
|
||||
⠈⡷⡄⠈⠲⢤⣈⠻⠉⠛⠉⠉⠁⠒⠖⠉⠉⠉⠒⠶⢦⣤⠴⠒⢉⣡⠴
|
||||
⠀⢸⡿⡂⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣴⡞⠉⠀⢀⣠⡞
|
||||
⠀⠀⢙⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⢠⡼⡟
|
||||
⠀⠀⡼⠋⠀⣤⣀⠀⠀⠀⠀⠀⠈⠐⣂⣄⠀⠀⠀⠀⠀⠀⠀⢀⠀⣰⡟⠁
|
||||
⠀⢠⡇⠀⠀⠘⠛⠃⠀⠀⠀⠀⠾⣿⠿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⢻
|
||||
⠀⢸⡇⢺⡀⠀⢠⡒⠠⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⡀⠀⠀⠸⡇
|
||||
⠀⢸⡇⣘⠑⡀⠀⠙⢏⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠂⠀⣔⣇
|
||||
⠀⢸⡇⡁⠀⢳⣶⣾⣷⣦⣄⣀⡀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿
|
||||
⠀⠸⡇⠁⠀⠀⢏⠉⠀⠀⠙⠛⠛⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⡏
|
||||
⠀⠀⠯⣀⣈⣀⣈⣐⣲⣄⣄⣤⣴⣆⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣈⣛⡧
|
||||
-->
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
Binary file not shown.
+63
-5
@@ -17,8 +17,10 @@ import useSidebarStore from "./stores/useSidebarStore";
|
||||
import { useEffect, useState } from "react";
|
||||
import ky from "ky";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import LoaderIcon from "./components/icons/LoaderIcon";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import AlertIcon from "./components/icons/AlertIcon";
|
||||
|
||||
function App() {
|
||||
const [isOpen] = useSidebarStore((state) => [state.isOpen]);
|
||||
@@ -35,16 +37,19 @@ function App() {
|
||||
const location = i18n.language === "ru" ? "a1" : "a2";
|
||||
const build = searchParams.get("build") || null;
|
||||
|
||||
const [setIsOpenSidebar] = useSidebarStore((state) => [state.setIsOpen]);
|
||||
|
||||
function toastError(text: string) {
|
||||
toast.error(text, {
|
||||
position: "bottom-right",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
position: "top-center",
|
||||
autoClose: 2000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "dark",
|
||||
icon: <AlertIcon />,
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -265,6 +270,59 @@ function App() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:mt-40 sm:mt-[120px] mt-[88px]">
|
||||
<div className=" grid xl:grid-cols-3 lg:grid-cols-2 lg:gap-4 gap-8">
|
||||
<div className="flex flex-col justify-between gap-8 pb-4 border-b border-[#3D425C]">
|
||||
<div className="flex flex-col xl:gap-8 gap-6">
|
||||
<p className="2xl:text-[64px] xl:text-5xl text-[40px] text-gradient font-gilroy w-fit leading-none font-medium">
|
||||
<Trans i18nKey={"signUp.title"}>
|
||||
Запись на
|
||||
<br />
|
||||
удаленную
|
||||
<br />
|
||||
демонстрацию
|
||||
</Trans>
|
||||
</p>
|
||||
<p className="2xl:text-base text-sm">
|
||||
<Trans i18nKey={"signUp.desc"}>
|
||||
Запись на демонстрацию может быть
|
||||
<br />
|
||||
оформлена в виде блока на сайте
|
||||
<br />
|
||||
застройщика или жилого комплекса.
|
||||
</Trans>
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsOpenSidebar(true)}
|
||||
className="group relative px-6 py-2 bg-gradient rounded-full lg:text-base text-sm font-medium leading-normal w-fit"
|
||||
>
|
||||
<div className="absolute top-0 left-0 w-full h-full rounded-full bg-black opacity-0 group-hover:opacity-10 transition-all"></div>
|
||||
<span className="relative">
|
||||
<Trans i18nKey={"signUp.button"}>Записаться</Trans>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<p className="2xl:text-sm text-xs text-[#52587A]">
|
||||
<Trans i18nKey={"signUp.notice"}>
|
||||
Запись доступна в демонстрационном режиме.
|
||||
<br />
|
||||
Указанные при записи данные не будут сохранены.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="xl:col-span-2">
|
||||
<video
|
||||
src="/videos/video.mp4"
|
||||
playsInline
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:mt-40 sm:mt-[120px] mt-[88px]">
|
||||
<div className="grid lg:grid-cols-4 grid-cols-1 lg:gap-4 gap-6">
|
||||
<div className="col-span-1">
|
||||
|
||||
+135
-85
@@ -54,94 +54,144 @@ function MonitoringPage() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen text-[#F2F2F2] p-4 gap-4 flex flex-col">
|
||||
{servers.map((server: any) => (
|
||||
<div key={server.id} className="p-4 bg-[#22222A]">
|
||||
<div>
|
||||
{differenceInSeconds(new Date(), parseISO(server.updatedAt)) >=
|
||||
10 ? (
|
||||
<p className="flex items-center gap-2">
|
||||
<span className="h-2 w-2 rounded-full bg-red-500"></span>
|
||||
<span>Не в сети</span>
|
||||
</p>
|
||||
) : (
|
||||
<p className="flex items-center gap-2">
|
||||
<span className="h-2 w-2 rounded-full bg-green-500"></span>
|
||||
<span>В сети</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<p>Локация: "{server.location}"</p>
|
||||
<p>Имя сервера: "{server.name}"</p>
|
||||
<p>Лимит процессов: {server.limit_process}</p>
|
||||
<div>
|
||||
<p>CPU: {server.cpu}</p>
|
||||
<p>RAM: {server.ram}</p>
|
||||
<p className="flex space-x-2">
|
||||
<span>GPU: </span>
|
||||
<span className="flex space-x-4">
|
||||
{server.gpu?.map((item: any, index: number) => (
|
||||
<span key={index}>
|
||||
{item} {index === 2 && "°C"}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="min-h-screen text-[#F2F2F2] p-4 flex flex-col">
|
||||
<div className="flex flex-col gap-2 border-b border-[#22222A] pb-4 text-sm">
|
||||
<p>Сервера:</p>
|
||||
<div className="grid lg:grid-cols-3 gap-2">
|
||||
{servers.length > 0 ? (
|
||||
servers.map((server: any) => (
|
||||
<div key={server.id} className="p-4 bg-[#22222A] rounded">
|
||||
<div>
|
||||
{differenceInSeconds(
|
||||
new Date(),
|
||||
parseISO(server.updatedAt)
|
||||
) >= 10 ? (
|
||||
<p className="flex items-center gap-2">
|
||||
<span className="h-2 w-2 rounded-full bg-red-500"></span>
|
||||
<span>Не в сети</span>
|
||||
</p>
|
||||
) : (
|
||||
<p className="flex items-center gap-2">
|
||||
<span className="h-2 w-2 rounded-full bg-green-500"></span>
|
||||
<span>В сети</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<p>
|
||||
Локация:{" "}
|
||||
<span className="font-medium">{server.location}</span>
|
||||
</p>
|
||||
<p>
|
||||
Имя сервера:{" "}
|
||||
<span className="font-medium">{server.name}</span>
|
||||
</p>
|
||||
<p>
|
||||
Лимит процессов:{" "}
|
||||
<span className="font-medium">{server.limit_process}</span>
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
CPU: <span className="font-medium">{server.cpu}</span>
|
||||
</p>
|
||||
<p>
|
||||
RAM: <span className="font-medium">{server.ram}</span>
|
||||
</p>
|
||||
{/* <p className="flex space-x-2">
|
||||
<span>GPU: </span>
|
||||
<span className="flex space-x-4">
|
||||
{server.gpu?.map((item: any, index: number) => (
|
||||
<span key={index}>
|
||||
{item} {index === 2 && "°C"}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p> */}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="opacity-50">Нет запущенных серверов</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{sessions.map((session: any) => (
|
||||
<div
|
||||
key={session.id}
|
||||
className="flex flex-wrap gap-4 justify-between items-center p-4 bg-[#22222A] rounded"
|
||||
>
|
||||
<div className="flex gap-4">
|
||||
<div className="">
|
||||
<QRCode
|
||||
bgColor="#22222A"
|
||||
fgColor="#F2F2F2"
|
||||
size={128}
|
||||
value={`https://stream.graff.tech/stream/?data=wss://${session.location}.sess.stream.graff.tech/${session.server}/${session.cirrusPort}/`}
|
||||
viewBox={`0 0 128 128`}
|
||||
/>
|
||||
</div>
|
||||
<div className="sm:text-base text-sm">
|
||||
<p>Локация: "{session.location}"</p>
|
||||
<p>Сервер: "{session.server}"</p>
|
||||
<p>Сборка: "{session.title}"</p>
|
||||
<p>Порт: {session.cirrusPort}</p>
|
||||
<p>Пользователи: {session.connectedPlayersCount || 0}</p>
|
||||
<p>
|
||||
Время запуска: {new Date(session.createdAt).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col gap-2 text-sm">
|
||||
<p>Запущенные сессии:</p>
|
||||
{sessions.length > 0 ? (
|
||||
sessions.map((session: any) => (
|
||||
<div
|
||||
key={session.id}
|
||||
className="flex flex-wrap gap-4 justify-between items-center p-4 bg-[#22222A] rounded"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="">
|
||||
<QRCode
|
||||
bgColor="#22222A"
|
||||
fgColor="#F2F2F2"
|
||||
size={128}
|
||||
value={`https://stream.graff.tech/stream/?data=wss://${session.location}.sess.stream.graff.tech/${session.server}/${session.cirrusPort}/`}
|
||||
viewBox={`0 0 128 128`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
Локация:{" "}
|
||||
<span className="font-medium">{session.location}</span>
|
||||
</p>
|
||||
<p>
|
||||
Сервер:{" "}
|
||||
<span className="font-medium">{session.server}</span>
|
||||
</p>
|
||||
<p>
|
||||
Сборка: <span className="font-medium">{session.title}</span>
|
||||
</p>
|
||||
<p>
|
||||
Порт:{" "}
|
||||
<span className="font-medium">{session.cirrusPort}</span>
|
||||
</p>
|
||||
<p>
|
||||
Пользователи:{" "}
|
||||
<span className="font-medium">
|
||||
{session.connectedPlayersCount || 0}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Дата и время запуска:{" "}
|
||||
<span className="font-medium">
|
||||
{new Date(session.createdAt).toLocaleString()}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-x-4">
|
||||
<a
|
||||
href={`https://stream.graff.tech/stream/${session.id}`}
|
||||
target="_blank"
|
||||
className="inline-block px-4 py-2 bg-blue-600 hover:bg-blue-500 transition-colors rounded"
|
||||
>
|
||||
Открыть в новом окне
|
||||
</a>
|
||||
<button
|
||||
onClick={() =>
|
||||
endActiveSession(
|
||||
session.location,
|
||||
session.server,
|
||||
session.uePort,
|
||||
session.cirrusPort
|
||||
)
|
||||
}
|
||||
className="px-4 py-2 bg-red-600 hover:bg-red-500 transition-colors rounded"
|
||||
>
|
||||
Завершить сессию
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="grid lg:grid-cols-1 grid-cols-2 gap-4 lg:w-auto w-full">
|
||||
<a
|
||||
href={`https://stream.graff.tech/stream/${session.id}`}
|
||||
target="_blank"
|
||||
className="inline-block text-center px-4 py-2 bg-blue-600 hover:bg-blue-500 transition-colors rounded"
|
||||
>
|
||||
Открыть в новом окне
|
||||
</a>
|
||||
<button
|
||||
onClick={() =>
|
||||
endActiveSession(
|
||||
session.location,
|
||||
session.server,
|
||||
session.uePort,
|
||||
session.cirrusPort
|
||||
)
|
||||
}
|
||||
className="px-4 py-2 bg-red-600 hover:bg-red-500 transition-colors rounded"
|
||||
>
|
||||
Завершить сессию
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="opacity-50">Нет запущенных сессий</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+42
-16
@@ -16,14 +16,18 @@ import ModalContainer from "./components/ModalContainer";
|
||||
import useModalStore from "./stores/useModalStore";
|
||||
import ShareModal from "./components/modals/ShareModal";
|
||||
import { Transition } from "react-transition-group";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import FullscreenIcon from "./components/icons/FullscreenIcon";
|
||||
import WindowedModeIcon from "./components/icons/WindowedModeIcon";
|
||||
import QRIcon from "./components/icons/QRIcon";
|
||||
import QRCodeModal from "./components/modals/QRCodeModal";
|
||||
import PersonsIcon from "./components/icons/PersonsIcon";
|
||||
import UsersManagementModal from "./components/modals/UsersManagementModal";
|
||||
import useStreamUserStore from "./stores/useStreamUserStore";
|
||||
import { ToastContainer, toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import UserIcon from "./components/icons/UserIcon";
|
||||
import HandOnIcon from "./components/icons/HandOnIcon";
|
||||
import AlertIcon from "./components/icons/AlertIcon";
|
||||
|
||||
function StreamPage() {
|
||||
const params = useParams();
|
||||
@@ -32,7 +36,10 @@ function StreamPage() {
|
||||
const [isStreamEnded, setIsStreamEnded] = useState<boolean>(false);
|
||||
const [isStreamLoaded, setStreamLoaded] = useState<boolean>(false);
|
||||
const [socket, setSocket] = useState<any>(null);
|
||||
const [users, setUsers] = useState<any>([]);
|
||||
const [users, setUsers] = useStreamUserStore((state) => [
|
||||
state.users,
|
||||
state.setUsers,
|
||||
]);
|
||||
const [me, setMe] = useState<any>({});
|
||||
|
||||
// const screen = useScreen();
|
||||
@@ -49,27 +56,43 @@ function StreamPage() {
|
||||
|
||||
function toastWarn(text: string) {
|
||||
toast.warn(text, {
|
||||
position: "bottom-right",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
position: "top-center",
|
||||
autoClose: 2000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "dark",
|
||||
icon: <AlertIcon />,
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
function toastInfo(text: string) {
|
||||
function toastUser(text: string) {
|
||||
toast.info(text, {
|
||||
position: "bottom-right",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
position: "top-center",
|
||||
autoClose: 2000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "dark",
|
||||
icon: <UserIcon />,
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
function toastHandOn(text: string) {
|
||||
toast.info(text, {
|
||||
position: "top-center",
|
||||
autoClose: 2000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
theme: "dark",
|
||||
icon: <HandOnIcon />,
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,7 +136,7 @@ function StreamPage() {
|
||||
setUsers(room.users);
|
||||
// console.log("join: ", _socketId, room.users);
|
||||
|
||||
toastInfo("Присоеденился новый участник");
|
||||
toastUser("Присоеденился новый участник");
|
||||
});
|
||||
|
||||
socket.on("update", (_socketId, room) => {
|
||||
@@ -126,7 +149,11 @@ function StreamPage() {
|
||||
});
|
||||
|
||||
socket.on("leave", (socketId) => {
|
||||
setUsers((prev: any) => prev.filter((user: any) => user.id !== socketId));
|
||||
setUsers(
|
||||
useStreamUserStore
|
||||
.getState()
|
||||
.users.filter((user: any) => user.id !== socketId)
|
||||
);
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -140,7 +167,7 @@ function StreamPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (me && me.allowControl && !me.admin) {
|
||||
toastInfo("Управление получено");
|
||||
toastHandOn("Управление получено");
|
||||
}
|
||||
}, [me]);
|
||||
|
||||
@@ -234,7 +261,6 @@ function StreamPage() {
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<UsersManagementModal
|
||||
users={users}
|
||||
me={me}
|
||||
handleUpdate={(socketId, params) => update(socketId, params)}
|
||||
handleKick={(socketId) => kick(socketId)}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Trans } from "react-i18next";
|
||||
import LogoIcon from "./icons/LogoIcon";
|
||||
import LogoMobileIcon from "./icons/LogoMobileIcon";
|
||||
import i18n from "../i18n";
|
||||
import useSidebarStore from "../stores/useSidebarStore";
|
||||
// import useSidebarStore from "../stores/useSidebarStore";
|
||||
|
||||
interface HeaderProps {
|
||||
handleChangeLang: (lang: string) => void;
|
||||
}
|
||||
|
||||
function Header({ handleChangeLang }: HeaderProps) {
|
||||
const [setIsOpen] = useSidebarStore((state) => [state.setIsOpen]);
|
||||
// const [setIsOpen] = useSidebarStore((state) => [state.setIsOpen]);
|
||||
|
||||
return (
|
||||
<header className="sm:py-6 py-4 flex justify-between">
|
||||
@@ -20,18 +19,6 @@ function Header({ handleChangeLang }: HeaderProps) {
|
||||
<LogoMobileIcon />
|
||||
</a>
|
||||
<div className="flex sm:gap-8 gap-2">
|
||||
<button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="group relative sm:px-8 px-6 py-2 bg-gradient rounded-full lg:text-base text-sm font-medium leading-normal"
|
||||
>
|
||||
<div className="absolute top-0 left-0 w-full h-full rounded-full bg-black opacity-0 group-hover:opacity-10 transition-all"></div>
|
||||
<span className="relative">
|
||||
<Trans i18nKey={"header.buttonFirst"}>Записаться</Trans>{" "}
|
||||
<span className="sm:inline hidden">
|
||||
<Trans i18nKey={"header.buttonSecond"}>на демонстрацию</Trans>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
className={[
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
function AlertIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Icon/Alert">
|
||||
<path
|
||||
id="Ellipse 224"
|
||||
d="M21 12C21 13.1819 20.7672 14.3522 20.3149 15.4442C19.8626 16.5361 19.1997 17.5282 18.364 18.364C17.5282 19.1997 16.5361 19.8626 15.4442 20.3149C14.3522 20.7672 13.1819 21 12 21C10.8181 21 9.64778 20.7672 8.55585 20.3149C7.46392 19.8626 6.47177 19.1997 5.63604 18.364C4.80031 17.5282 4.13738 16.5361 3.68508 15.4442C3.23279 14.3522 3 13.1819 3 12C3 10.8181 3.23279 9.64778 3.68508 8.55585C4.13738 7.46392 4.80031 6.47177 5.63604 5.63604C6.47177 4.80031 7.46392 4.13738 8.55585 3.68508C9.64778 3.23279 10.8181 3 12 3C13.1819 3 14.3522 3.23279 15.4442 3.68508C16.5361 4.13738 17.5282 4.80031 18.364 5.63604C19.1997 6.47177 19.8626 7.46392 20.3149 8.55585C20.7672 9.64778 21 10.8181 21 12L21 12Z"
|
||||
stroke="#F2F2F2"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<path
|
||||
id="Vector 1834"
|
||||
d="M12 7V13"
|
||||
stroke="#F2F2F2"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<circle id="Ellipse 225" cx="12" cy="17" r="1" fill="#F2F2F2" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default AlertIcon;
|
||||
@@ -4,6 +4,7 @@ import { ToastContainer, toast } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import useModalStore from "../../stores/useModalStore";
|
||||
import CloseIcon from "../icons/CloseIcon";
|
||||
import AlertIcon from "../icons/AlertIcon";
|
||||
|
||||
function ShareModal() {
|
||||
const [setModal] = useModalStore((state) => [state.setModal]);
|
||||
@@ -11,14 +12,15 @@ function ShareModal() {
|
||||
|
||||
function toastInfo(text: string) {
|
||||
toast.info(text, {
|
||||
position: "bottom-right",
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
position: "top-center",
|
||||
autoClose: 2000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
theme: "dark",
|
||||
icon: <AlertIcon />,
|
||||
closeButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,80 +8,86 @@ import HandOnIcon from "../icons/HandOnIcon";
|
||||
import MobilePhoneIcon from "../icons/MobilePhoneIcon";
|
||||
import UserIcon from "../icons/UserIcon";
|
||||
import CloseIcon from "../icons/CloseIcon";
|
||||
import useStreamUserStore from "../../stores/useStreamUserStore";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
interface UsersManagementModalProps {
|
||||
users: any[];
|
||||
me: any;
|
||||
handleUpdate: (socketId: string, params: any) => void;
|
||||
handleKick: (socketId: string) => void;
|
||||
}
|
||||
|
||||
function UsersManagementModal({
|
||||
users,
|
||||
me,
|
||||
handleUpdate,
|
||||
handleKick,
|
||||
}: UsersManagementModalProps) {
|
||||
const [setModal] = useModalStore((state) => [state.setModal]);
|
||||
const [users] = useStreamUserStore((state) => [state.users]);
|
||||
|
||||
return (
|
||||
<div className="relative lg:p-10 p-6 bg-[#131317] rounded shadow-lg w-[320px] flex flex-col gap-2">
|
||||
{users.map((user: any, index: number) => (
|
||||
<div key={index} className="relative">
|
||||
<div className="flex items-center gap-2 text-white">
|
||||
<div className="flex flex-col">
|
||||
{user.admin ? <AdminUserIcon /> : <UserIcon />}
|
||||
<div className="flex flex-col lg:gap-8 gap-4">
|
||||
<p className="font-gilroy lg:text-2xl">Участники</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
{users.map((user: any, index: number) => (
|
||||
<div key={index} className="relative">
|
||||
<div className="flex items-center gap-2 text-white">
|
||||
<div className="relative flex flex-col">
|
||||
{user.admin ? <AdminUserIcon /> : <UserIcon />}
|
||||
</div>
|
||||
|
||||
{me && me.id === user.id && (
|
||||
<span className="text-[#73788C] text-sm">Вы</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{new userAgentParser().getDevice().type !== "mobile" ? (
|
||||
<DesktopIcon />
|
||||
) : (
|
||||
<MobilePhoneIcon />
|
||||
)}
|
||||
|
||||
<span className="text-sm">{user.city}</span>
|
||||
|
||||
{!user.admin && (
|
||||
<>
|
||||
{!user.allowControl ? (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleUpdate(user.id, { allowControl: true })
|
||||
}
|
||||
className="outline-none"
|
||||
>
|
||||
<HandOffIcon />
|
||||
</button>
|
||||
{new userAgentParser(user.ua).getDevice().type !== "mobile" ? (
|
||||
<DesktopIcon />
|
||||
) : (
|
||||
<MobilePhoneIcon />
|
||||
)}
|
||||
|
||||
<span className="text-sm">{user.city}</span>
|
||||
|
||||
{!user.admin && (
|
||||
<>
|
||||
{!user.allowControl ? (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleUpdate(user.id, { allowControl: true })
|
||||
}
|
||||
className="outline-none"
|
||||
>
|
||||
<HandOffIcon />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleUpdate(user.id, { allowControl: false })
|
||||
}
|
||||
className="outline-none"
|
||||
>
|
||||
<HandOnIcon />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{me && me.admin && !user.admin && (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleUpdate(user.id, { allowControl: false })
|
||||
}
|
||||
onClick={() => handleKick(user.id)}
|
||||
className="outline-none"
|
||||
>
|
||||
<HandOnIcon />
|
||||
<ExitIcon />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{me && me.admin && !user.admin && (
|
||||
<button
|
||||
onClick={() => handleKick(user.id)}
|
||||
className="outline-none"
|
||||
>
|
||||
<ExitIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative"></div>
|
||||
<div>
|
||||
{me && me.id === user.id && (
|
||||
<span className="text-[#73788C] text-xs">Вы</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setModal(null)}
|
||||
|
||||
+16
-2
@@ -38,6 +38,13 @@ const resources = {
|
||||
button: "Запустить",
|
||||
},
|
||||
},
|
||||
signUp: {
|
||||
title: "Запись на<br />удаленную<br />демонстрацию",
|
||||
desc: "Запись на демонстрацию может быть<br />оформлена в виде блока на сайте<br />застройщика или жилого комплекса.",
|
||||
button: "Записаться",
|
||||
notice:
|
||||
"Запись доступна в демонстрационном режиме.<br />Указанные при записи данные не будут сохранены.",
|
||||
},
|
||||
feedback: {
|
||||
title: "Свяжитесь<br />с нами",
|
||||
desc: "Хотите увеличить конверсию?<br />Давайте обсудим детали!",
|
||||
@@ -131,6 +138,13 @@ const resources = {
|
||||
button: "Run demo",
|
||||
},
|
||||
},
|
||||
signUp: {
|
||||
title: "Sign up for a<br />remote demo",
|
||||
desc: "Registration for a demonstration can be<br />designed as a block on the site<br />developer or residential complex.",
|
||||
button: "Sign up",
|
||||
notice:
|
||||
"The recording is available in demo mode.<br />The data specified during recording will not be saved.",
|
||||
},
|
||||
feedback: {
|
||||
title: "Contact us",
|
||||
desc: "Want to increase conversion?<br />Let's discuss the details!",
|
||||
@@ -200,11 +214,11 @@ void i18n
|
||||
.use(initReactI18next) // passes i18n down to react-i18next
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: ["ru", "en"],
|
||||
fallbackLng: ["ru"],
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss
|
||||
},
|
||||
debug: true,
|
||||
// debug: true,
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -26,3 +26,19 @@ input {
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.Toastify__toast-theme--dark {
|
||||
background: #151619 !important;
|
||||
font-family: "Inter", sans-serif !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.Toastify__toast-icon {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
background: linear-gradient(23deg, #798fff 16.71%, #d375ff 96.35%) !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { create } from "zustand";
|
||||
import { devtools } from "zustand/middleware";
|
||||
|
||||
interface StreamUserState {
|
||||
users: any[];
|
||||
setUsers: (users: any[]) => void;
|
||||
}
|
||||
|
||||
const useStreamUserStore = create<StreamUserState>()(
|
||||
devtools((set) => ({
|
||||
users: [],
|
||||
setUsers: (users) => set({ users }),
|
||||
}))
|
||||
);
|
||||
|
||||
export default useStreamUserStore;
|
||||
Reference in New Issue
Block a user