Add react-qr-code dependency; enhance PopupHeader and SharePopup components with draggable functionality; update LinkShare component for improved UI; integrate SettingsModal in HomePage for better user experience.
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
"ky": "^1.11.0",
|
"ky": "^1.11.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-qr-code": "^2.0.18",
|
||||||
"react-router": "^7.9.3",
|
"react-router": "^7.9.3",
|
||||||
"zustand": "^5.0.8",
|
"zustand": "^5.0.8",
|
||||||
},
|
},
|
||||||
@@ -404,6 +405,8 @@
|
|||||||
|
|
||||||
"jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
|
"jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
|
||||||
|
|
||||||
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||||
|
|
||||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||||
@@ -426,6 +429,8 @@
|
|||||||
|
|
||||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||||
|
|
||||||
|
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||||
|
|
||||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||||
@@ -496,14 +501,22 @@
|
|||||||
|
|
||||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
||||||
|
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
|
"qr.js": ["qr.js@0.0.0", "", {}, "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ=="],
|
||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
|
|
||||||
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||||
|
|
||||||
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
||||||
|
|
||||||
|
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|
||||||
|
"react-qr-code": ["react-qr-code@2.0.18", "", { "dependencies": { "prop-types": "^15.8.1", "qr.js": "0.0.0" }, "peerDependencies": { "react": "*" } }, "sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg=="],
|
||||||
|
|
||||||
"react-router": ["react-router@7.9.3", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg=="],
|
"react-router": ["react-router@7.9.3", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg=="],
|
||||||
|
|
||||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"ky": "^1.11.0",
|
"ky": "^1.11.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-qr-code": "^2.0.18",
|
||||||
"react-router": "^7.9.3",
|
"react-router": "^7.9.3",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
import usePopupStore from "../store/popupStore";
|
import usePopupStore from "../store/popupStore";
|
||||||
import XMarkIcon from "./icons/XMarkIcon";
|
import XMarkIcon from "./icons/XMarkIcon";
|
||||||
import Button from "./ui/Button";
|
import Button from "./ui/Button";
|
||||||
@@ -6,15 +7,24 @@ interface PopupHeaderProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
leftButton?: React.ReactNode;
|
leftButton?: React.ReactNode;
|
||||||
headerRef: React.RefObject<HTMLDivElement | null>;
|
headerRef: React.RefObject<HTMLDivElement | null>;
|
||||||
|
draggable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopupHeader({ title, leftButton, headerRef }: PopupHeaderProps) {
|
function PopupHeader({
|
||||||
|
title,
|
||||||
|
leftButton,
|
||||||
|
headerRef,
|
||||||
|
draggable,
|
||||||
|
}: PopupHeaderProps) {
|
||||||
const { setPopup } = usePopupStore();
|
const { setPopup } = usePopupStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={headerRef}
|
ref={headerRef}
|
||||||
className="2xl:p-[1.111vw] p-4 flex justify-between items-center cursor-grab select-none relative"
|
className={clsx(
|
||||||
|
"2xl:p-[1.111vw] p-4 flex justify-between items-center cursor-graba select-none relative",
|
||||||
|
draggable && "cursor-grab active:cursor-grabbing"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="2xl:size-[2.222vw] size-8">{leftButton}</div>
|
<div className="2xl:size-[2.222vw] size-8">{leftButton}</div>
|
||||||
{title && (
|
{title && (
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ function PopupWrapper({
|
|||||||
headerRef={headerRef}
|
headerRef={headerRef}
|
||||||
title={title}
|
title={title}
|
||||||
leftButton={leftButton}
|
leftButton={leftButton}
|
||||||
|
draggable={draggable}
|
||||||
/>
|
/>
|
||||||
<div className="2xl:p-[1.389vw] p-5">{children}</div>
|
<div className="2xl:p-[1.389vw] p-5">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import SoundIcon from "../icons/SoundIcon";
|
||||||
|
import VideoFilledIcon from "../icons/VideoFilledIcon";
|
||||||
|
import ModalWrapper from "../ModalWrapper";
|
||||||
|
import Button from "../ui/Button";
|
||||||
|
import RangeInput from "../ui/RangeInput";
|
||||||
|
|
||||||
|
function SettingsModal() {
|
||||||
|
const [microphoneVolume, setMicrophoneVolume] = useState(50);
|
||||||
|
// const [speakerVolume, setSpeakerVolume] = useState(50);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalWrapper title="Настройки">
|
||||||
|
<div className="2xl:space-y-[1.389vw] space-y-5">
|
||||||
|
<div className="flex">
|
||||||
|
<Button variant="menu" size="large" className="w-full">
|
||||||
|
<div className="flex 2xl:gap-[0.556vw] items-center">
|
||||||
|
<div className="2xl:size-[1.111vw] size-4 text-[#7B60F3]">
|
||||||
|
<SoundIcon />
|
||||||
|
</div>
|
||||||
|
<p className="font-medium">Звук</p>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<Button variant="menu" size="large" className="w-full">
|
||||||
|
<div className="flex 2xl:gap-[0.556vw] items-center">
|
||||||
|
<div className="2xl:size-[1.111vw] size-4 text-[#7B60F3]">
|
||||||
|
<VideoFilledIcon />
|
||||||
|
</div>
|
||||||
|
<p className="font-medium">Видео</p>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="2xl:space-y-[1.667vw] space-y-6">
|
||||||
|
<div className="2xl:space-y-[0.833vw] space-y-3">
|
||||||
|
<p className="title-s font-medium">Микрофон</p>
|
||||||
|
<div className="2xl:space-y-[1.111vw] space-y-4">
|
||||||
|
<div className="flex 2xl:gap-[0.556vw]">
|
||||||
|
<div className="bg-[#F3F3F3] flex-1"></div>
|
||||||
|
<Button variant="cta" size="large">
|
||||||
|
Проверить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center 2xl:gap-[0.833vw]">
|
||||||
|
<div className="2xl:size-[1.111vw] size-4 text-[#7D7D7D]">
|
||||||
|
<SoundIcon />
|
||||||
|
</div>
|
||||||
|
<RangeInput
|
||||||
|
value={microphoneVolume}
|
||||||
|
onChange={setMicrophoneVolume}
|
||||||
|
/>
|
||||||
|
<p className="caption-xs font-medium text-[#7D7D7D] 2xl:w-[1.667vw] w-6">
|
||||||
|
{microphoneVolume.toFixed(0)}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr className="bg-[#F6F6F6] 2xl:h-[0.069vw] h-px" />
|
||||||
|
<div className="2xl:space-y-[0.833vw] space-y-3">
|
||||||
|
<p className="title-s font-medium">Динамик</p>
|
||||||
|
<div className="2xl:space-y-[1.111vw] space-y-4">
|
||||||
|
<div className="flex 2xl:gap-[0.556vw]">
|
||||||
|
<div className="bg-[#F3F3F3] flex-1"></div>
|
||||||
|
<Button variant="cta" size="large">
|
||||||
|
Проверить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsModal;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import PopupWrapper from "../PopupWrapper";
|
||||||
|
import Button from "../ui/Button";
|
||||||
|
import ChevronLeftIcon from "../icons/ChevronLeftIcon";
|
||||||
|
import usePopupStore from "../../store/popupStore";
|
||||||
|
import SharePopup from "./SharePopup";
|
||||||
|
import QRCode from "react-qr-code";
|
||||||
|
|
||||||
|
interface QRCodePopupProps {
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function QRCodePopup({ link }: QRCodePopupProps) {
|
||||||
|
const { setPopup } = usePopupStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopupWrapper
|
||||||
|
draggable
|
||||||
|
leftButton={
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setPopup(<SharePopup link={link} />)}
|
||||||
|
>
|
||||||
|
<div className="2xl:size-[1.111vw] size-4">
|
||||||
|
<ChevronLeftIcon />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col 2xl:gap-y-[1.667vw] gap-y-6 items-center">
|
||||||
|
<QRCode value={link} className="2xl:size-[10.556vw] size-[152px]" />
|
||||||
|
<div className="2xl:space-y-[0.556vw] space-y-2">
|
||||||
|
<p className="title-m font-medium">Подключайтесь к сеансу</p>
|
||||||
|
<p className="caption-s font-medium text-center text-[#CCCCCC]">
|
||||||
|
Можно даже с мобильным интернетом
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopupWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QRCodePopup;
|
||||||
@@ -3,14 +3,22 @@ import ShareFilledIcon from "../icons/ShareFilledIcon";
|
|||||||
import PopupWrapper from "../PopupWrapper";
|
import PopupWrapper from "../PopupWrapper";
|
||||||
import Button from "../ui/Button";
|
import Button from "../ui/Button";
|
||||||
import LinkShare from "../ui/LinkShare";
|
import LinkShare from "../ui/LinkShare";
|
||||||
|
import usePopupStore from "../../store/popupStore";
|
||||||
|
import QRCodePopup from "./QRCodePopup";
|
||||||
|
|
||||||
|
function SharePopup({ link }: { link: string }) {
|
||||||
|
const { setPopup } = usePopupStore();
|
||||||
|
|
||||||
function SharePopup() {
|
|
||||||
return (
|
return (
|
||||||
<PopupWrapper
|
<PopupWrapper
|
||||||
title="Пригласить"
|
title="Пригласить"
|
||||||
draggable
|
draggable
|
||||||
leftButton={
|
leftButton={
|
||||||
<Button variant="secondary" size="small">
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setPopup(<QRCodePopup link={link} />)}
|
||||||
|
>
|
||||||
<div className="2xl:size-[1.111vw] size-4">
|
<div className="2xl:size-[1.111vw] size-4">
|
||||||
<QRIcon />
|
<QRIcon />
|
||||||
</div>
|
</div>
|
||||||
@@ -19,7 +27,7 @@ function SharePopup() {
|
|||||||
>
|
>
|
||||||
<div className="mb-[1.389vw]">
|
<div className="mb-[1.389vw]">
|
||||||
<p className="title-s mb-[0.833vw] font-medium">Скопировать ссылку</p>
|
<p className="title-s mb-[0.833vw] font-medium">Скопировать ссылку</p>
|
||||||
<LinkShare link={"https://estate.stream/ahdy12jdco1"} />
|
<LinkShare link={link} />
|
||||||
</div>
|
</div>
|
||||||
<Button variant="primary" size="large" className="w-full">
|
<Button variant="primary" size="large" className="w-full">
|
||||||
Отправить
|
Отправить
|
||||||
|
|||||||
@@ -18,40 +18,42 @@ export default function LinkShare({ link }: { link: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-[3.75vw] bg-[#F3F3F3] flex items-center justify-between gap-[0.833vw] px-[1.111vw] rounded-[1.111vw] relative">
|
<div className="flex flex-col gap-[0.556vw]">
|
||||||
<span
|
<div className="w-full h-[3.75vw] bg-[#F3F3F3] flex items-center justify-between gap-[0.833vw] px-[1.111vw] rounded-[1.111vw] relative">
|
||||||
className="text-ellipsis text-s hover:cursor-pointer overflow-hidden"
|
<span
|
||||||
onClick={handleCopy}
|
className="text-ellipsis text-s hover:cursor-pointer overflow-hidden"
|
||||||
>
|
|
||||||
{link}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{shareState === "default" && (
|
|
||||||
<Button
|
|
||||||
variant="cta"
|
|
||||||
size="medium"
|
|
||||||
className="translate-x-[0.556vw]"
|
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
>
|
>
|
||||||
Копировать
|
{link}
|
||||||
</Button>
|
</span>
|
||||||
)}
|
|
||||||
|
|
||||||
{shareState === "loading" && (
|
{shareState === "default" && (
|
||||||
<div className="size-[1.389vw] text-[#7B60F3] animate-spin">
|
<Button
|
||||||
<LoaderIcon />
|
variant="cta"
|
||||||
</div>
|
size="medium"
|
||||||
)}
|
className="translate-x-[0.556vw]"
|
||||||
|
onClick={handleCopy}
|
||||||
|
>
|
||||||
|
Копировать
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
{shareState === "done" && (
|
{shareState === "loading" && (
|
||||||
<>
|
<div className="size-[1.389vw] text-[#7B60F3] animate-spin">
|
||||||
|
<LoaderIcon />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shareState === "done" && (
|
||||||
<div className="size-[1.389vw] text-[#7B60F3]">
|
<div className="size-[1.389vw] text-[#7B60F3]">
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className="caption-s absolute bottom-[-1.25vw] text-[#29AF61] left-0">
|
)}
|
||||||
Ссылка скопирована
|
</div>
|
||||||
</div>
|
{shareState === "done" && (
|
||||||
</>
|
<div className="caption-s absolutea bottom-[-1.25vw] text-[#29AF61] left-0">
|
||||||
|
Ссылка скопирована
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
interface RangeInputProps {
|
||||||
|
max?: number;
|
||||||
|
min?: number;
|
||||||
|
value?: number;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RangeInput({
|
||||||
|
onChange,
|
||||||
|
value = 50,
|
||||||
|
max = 100,
|
||||||
|
min = 0,
|
||||||
|
}: RangeInputProps) {
|
||||||
|
const [mouseDown, setMouseDown] = useState(false);
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addEventListener("mouseup", () => setMouseDown(false));
|
||||||
|
addEventListener("mousemove", handleMouseMove);
|
||||||
|
return () => {
|
||||||
|
removeEventListener("mouseup", () => setMouseDown(false));
|
||||||
|
removeEventListener("mousemove", handleMouseMove);
|
||||||
|
};
|
||||||
|
}, [handleMouseMove]);
|
||||||
|
|
||||||
|
function handleMouseMove(e: MouseEvent) {
|
||||||
|
if (mouseDown && ref.current) {
|
||||||
|
onChange(
|
||||||
|
Math.min(
|
||||||
|
Math.max(
|
||||||
|
min,
|
||||||
|
((e.clientX - ref.current.getBoundingClientRect().left) /
|
||||||
|
ref.current.clientWidth) *
|
||||||
|
(max - min) +
|
||||||
|
min
|
||||||
|
),
|
||||||
|
max
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseDown(e: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (ref.current) {
|
||||||
|
onChange(
|
||||||
|
Math.min(
|
||||||
|
Math.max(
|
||||||
|
min,
|
||||||
|
((e.clientX - ref.current.getBoundingClientRect().left) /
|
||||||
|
ref.current.clientWidth) *
|
||||||
|
(max - min) +
|
||||||
|
min
|
||||||
|
),
|
||||||
|
max
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setMouseDown(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className="2xl:w-[21.111vw] w-[304px] 2xl:h-[0.139vw] h-[2px] relative bg-[#F0F0F0] 2xl:rounded-[0.556vw] rounded-lg cursor-grab active:cursor-grabbing"
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-[#7B60F3] 2xl:rounded-[0.556vw] h-full absolute left-0 top-0 rounded-lg"
|
||||||
|
style={{ width: `${((value - min) / (max - min)) * 100}%` }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="2xl:size-[0.833vw] size-3 rounded-full bg-[#7B60F3] absolute top-1/2 -translate-y-1/2"
|
||||||
|
style={{ left: `${((value - min) / (max - min)) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RangeInput;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
interface SwitchProps {
|
||||||
|
enabled: boolean;
|
||||||
|
onChange: (enabled: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Switch({ enabled, onChange }: SwitchProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => onChange(!enabled)}
|
||||||
|
className={clsx(
|
||||||
|
"2xl:rounded-[0.833vw] rounded-xl 2xl:w-[2.778vw] w-10 2xl:py-[0.139vw] py-[2px] cursor-pointer transition-colors",
|
||||||
|
enabled ? "bg-[#7B60F3]" : "bg-[#F0F0F0]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"rounded-full 2xl:size-[1.389vw] size-5 bg-white shadow-[0_4px_40px_0_rgba(15,16,17,0.1)] transition-all",
|
||||||
|
enabled
|
||||||
|
? "2xl:translate-x-[1.25vw] translate-x-[18px]"
|
||||||
|
: "2xl:translate-x-[0.139vw] translate-x-[2px]"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Switch;
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
import ChatPopup from "../components/popups/ChatPopup";
|
|
||||||
=======
|
|
||||||
>>>>>>> 8aef8a530bcdd53af4add911e773f2691e0027e4
|
|
||||||
import Button from "../components/ui/Button";
|
import Button from "../components/ui/Button";
|
||||||
import FloatingActionButton from "../components/ui/FloatingActionButton";
|
import FloatingActionButton from "../components/ui/FloatingActionButton";
|
||||||
import { useMe, useLogout } from "../hooks/useAuth";
|
import { useMe, useLogout } from "../hooks/useAuth";
|
||||||
@@ -9,6 +5,9 @@ import { useNavigate } from "react-router";
|
|||||||
import ShareFilledIcon from "../components/icons/ShareFilledIcon";
|
import ShareFilledIcon from "../components/icons/ShareFilledIcon";
|
||||||
import SharePopup from "../components/popups/SharePopup";
|
import SharePopup from "../components/popups/SharePopup";
|
||||||
import usePopupStore from "../store/popupStore";
|
import usePopupStore from "../store/popupStore";
|
||||||
|
import SettingsModal from "../components/modals/SettingsModal";
|
||||||
|
import useModalStore from "../store/modalStore";
|
||||||
|
import CogFilledIcon from "../components/icons/CogFilledIcon";
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
const { data: user } = useMe();
|
const { data: user } = useMe();
|
||||||
@@ -21,6 +20,7 @@ function HomePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { setPopup } = usePopupStore();
|
const { setPopup } = usePopupStore();
|
||||||
|
const { setModal } = useModalStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-8 min-h-screen bg-gray-50">
|
<div className="py-8 min-h-screen bg-gray-50">
|
||||||
@@ -32,14 +32,26 @@ function HomePage() {
|
|||||||
|
|
||||||
<FloatingActionButton
|
<FloatingActionButton
|
||||||
variant="default"
|
variant="default"
|
||||||
// onClick={() => setModal(<ShareModal />)}
|
onClick={() =>
|
||||||
onClick={() => setPopup(<SharePopup />)}
|
setPopup(
|
||||||
|
<SharePopup link={"https://estate.stream/ahdy12jdco1"} />
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className="2xl:size-[1.111vw] size-4 text-white">
|
<div className="2xl:size-[1.111vw] size-4 text-white">
|
||||||
<ShareFilledIcon />
|
<ShareFilledIcon />
|
||||||
</div>
|
</div>
|
||||||
</FloatingActionButton>
|
</FloatingActionButton>
|
||||||
|
|
||||||
|
<FloatingActionButton
|
||||||
|
variant="default"
|
||||||
|
onClick={() => setModal(<SettingsModal />)}
|
||||||
|
>
|
||||||
|
<div className="2xl:size-[1.111vw] size-4 text-white">
|
||||||
|
<CogFilledIcon />
|
||||||
|
</div>
|
||||||
|
</FloatingActionButton>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="p-4 bg-blue-50 rounded-lg border border-blue-200">
|
<div className="p-4 bg-blue-50 rounded-lg border border-blue-200">
|
||||||
<h2 className="mb-2 text-xl font-semibold">
|
<h2 className="mb-2 text-xl font-semibold">
|
||||||
|
|||||||
Reference in New Issue
Block a user