Merge branch 'main' of http://192.168.1.163:3000/inmake/stream.graff.tech-new
This commit is contained in:
@@ -1,5 +1,63 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useEffect, useRef } from "react";
|
||||
import useModalStore from "../store/modalStore";
|
||||
import clsx from "clsx";
|
||||
|
||||
function ModalContainer() {
|
||||
return <div></div>;
|
||||
const { modal, setModal, position } = useModalStore();
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const backdropRef = useRef<HTMLDivElement>(null);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function handleResize() {
|
||||
if (!modalRef.current) return;
|
||||
|
||||
if (divRef.current!.clientHeight > modalRef.current!.clientHeight) {
|
||||
backdropRef.current!.style.height = `${divRef.current!.clientHeight}px`;
|
||||
} else {
|
||||
backdropRef.current!.style.height = `100%`;
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key !== "Escape") return;
|
||||
setModal(null);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", handleResize);
|
||||
window.addEventListener("keydown", handleKeydown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
window.removeEventListener("keydown", handleKeydown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
modal && (
|
||||
<div className="h-full">
|
||||
<div
|
||||
ref={modalRef}
|
||||
className={clsx(
|
||||
"bg-black/70 max-md:top-14 flex overflow-y-auto fixed inset-0 z-10 items-center",
|
||||
position === "center" ? "justify-center" : "justify-end"
|
||||
)}
|
||||
>
|
||||
<div className="max-h-full">
|
||||
<div ref={divRef} className="2xl:p-[1.111vw]">
|
||||
<div
|
||||
ref={backdropRef}
|
||||
className="absolute inset-0 cursor-pointer"
|
||||
onClick={() => setModal(null)}
|
||||
/>
|
||||
{modal}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalContainer;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import Button from "./ui/Button";
|
||||
import useModalStore from "../store/modalStore";
|
||||
import XMarkIcon from "./icons/XMarkIcon";
|
||||
|
||||
interface ModalHeaderProps {
|
||||
title?: string;
|
||||
leftButton?: React.ReactNode;
|
||||
}
|
||||
|
||||
function ModalHeader({ title, leftButton }: ModalHeaderProps) {
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
return (
|
||||
<div className="2xl:p-[1.111vw] p-4 flex justify-between items-center">
|
||||
<div className="2xl:size-[2.222vw] size-8">{leftButton}</div>
|
||||
{title && (
|
||||
<p className="title-s flex-1 font-medium text-center">{title}</p>
|
||||
)}
|
||||
<Button variant="secondary" size="small" onClick={() => setModal(null)}>
|
||||
<div className="2xl:size-[1.111vw] size-4">
|
||||
<XMarkIcon />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalHeader;
|
||||
@@ -0,0 +1,25 @@
|
||||
import ModalHeader from "./ModalHeader";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface ModalWrapperProps {
|
||||
children: React.ReactNode;
|
||||
title?: string;
|
||||
leftButton?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ModalWrapper({
|
||||
children,
|
||||
title,
|
||||
leftButton,
|
||||
className,
|
||||
}: ModalWrapperProps) {
|
||||
return (
|
||||
<div className={clsx("bg-white rounded-[1.111vw] relative", className)}>
|
||||
<ModalHeader title={title} leftButton={leftButton} />
|
||||
<div className="2xl:p-[1.389vw] p-5">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalWrapper;
|
||||
@@ -0,0 +1,13 @@
|
||||
import usePopupStore from "../store/popupStore";
|
||||
|
||||
function PopupContainer() {
|
||||
const { popup, position } = usePopupStore();
|
||||
|
||||
return (
|
||||
<div className="absolute" style={{ top: position.y, left: position.x }}>
|
||||
{popup}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PopupContainer;
|
||||
@@ -0,0 +1,33 @@
|
||||
import usePopupStore from "../store/popupStore";
|
||||
import XMarkIcon from "./icons/XMarkIcon";
|
||||
import Button from "./ui/Button";
|
||||
|
||||
interface PopupHeaderProps {
|
||||
title?: string;
|
||||
leftButton?: React.ReactNode;
|
||||
headerRef: React.RefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
function PopupHeader({ title, leftButton, headerRef }: PopupHeaderProps) {
|
||||
const { setPopup } = usePopupStore();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={headerRef}
|
||||
className="2xl:p-[1.111vw] p-4 flex justify-between items-center cursor-grab select-none relative"
|
||||
>
|
||||
<div className="2xl:size-[2.222vw] size-8">{leftButton}</div>
|
||||
{title && (
|
||||
<p className="title-s flex-1 font-medium text-center">{title}</p>
|
||||
)}
|
||||
<Button variant="secondary" size="small" onClick={() => setPopup(null)}>
|
||||
<div className="2xl:size-[1.111vw] size-4">
|
||||
<XMarkIcon />
|
||||
</div>
|
||||
</Button>
|
||||
<hr className="bg-[#F2F2F2] 2xl:h-[0.069vw] h-px absolute bottom-0 w-[calc(100%-2.778vw)] left-[1.389vw]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PopupHeader;
|
||||
@@ -0,0 +1,92 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import clsx from "clsx";
|
||||
import PopupHeader from "./PopupHeader";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import usePopupStore from "../store/popupStore";
|
||||
|
||||
interface PopupWrapperProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
title?: string;
|
||||
leftButton?: React.ReactNode;
|
||||
draggable?: boolean;
|
||||
}
|
||||
|
||||
function PopupWrapper({
|
||||
children,
|
||||
className,
|
||||
title,
|
||||
leftButton,
|
||||
draggable,
|
||||
}: PopupWrapperProps) {
|
||||
const { position, setPosition } = usePopupStore();
|
||||
const [mouseDown, setMouseDown] = useState(false);
|
||||
const [mouseDownPosition, setMouseDownPosition] = useState(position);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
addEventListener("mouseup", () => setMouseDown(false));
|
||||
return () => removeEventListener("mouseup", () => setMouseDown(false));
|
||||
}, []);
|
||||
|
||||
function handleMouseMove(e: MouseEvent) {
|
||||
if (draggable && mouseDown && wrapperRef.current) {
|
||||
e.preventDefault();
|
||||
setPosition({
|
||||
x: Math.min(
|
||||
Math.max(0, position.x + e.clientX - mouseDownPosition.x),
|
||||
window.innerWidth - wrapperRef.current.clientWidth
|
||||
),
|
||||
y: Math.min(
|
||||
Math.max(0, position.y + e.clientY - mouseDownPosition.y),
|
||||
window.innerHeight - wrapperRef.current.clientHeight
|
||||
),
|
||||
});
|
||||
setMouseDownPosition({ x: e.clientX, y: e.clientY });
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
addEventListener("mousemove", handleMouseMove);
|
||||
return () => removeEventListener("mousemove", handleMouseMove);
|
||||
}, [handleMouseMove]);
|
||||
|
||||
useEffect(() => {
|
||||
if (headerRef.current) {
|
||||
headerRef.current.addEventListener("mousedown", (e) => {
|
||||
setMouseDown(true);
|
||||
setMouseDownPosition({ x: e.clientX, y: e.clientY });
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
if (headerRef.current) {
|
||||
headerRef.current.removeEventListener("mousedown", (e) => {
|
||||
setMouseDown(true);
|
||||
setMouseDownPosition({ x: e.clientX, y: e.clientY });
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className={clsx(
|
||||
"2xl:rounded-[2.222vw] rounded-[32px] relative bg-white shadow-[0_4px_40px_0_rgba(15,16,17,0.1)]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<PopupHeader
|
||||
headerRef={headerRef}
|
||||
title={title}
|
||||
leftButton={leftButton}
|
||||
/>
|
||||
<div className="2xl:p-[1.389vw] p-5">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PopupWrapper;
|
||||
@@ -1,24 +1,34 @@
|
||||
import QRIcon from "../icons/QRIcon";
|
||||
import ShareFilledIcon from "../icons/ShareFilledIcon";
|
||||
import PopupWrapper from "../PopupWrapper";
|
||||
import Button from "../ui/Button";
|
||||
import LinkShare from "../ui/LinkShare";
|
||||
import ShareFilledIcon from "../icons/ShareFilledIcon";
|
||||
|
||||
export default function SharePopup() {
|
||||
function SharePopup() {
|
||||
return (
|
||||
<>
|
||||
<PopupWrapper
|
||||
title="Пригласить"
|
||||
draggable
|
||||
leftButton={
|
||||
<Button variant="secondary" size="small">
|
||||
<div className="2xl:size-[1.111vw] size-4">
|
||||
<QRIcon />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="mb-[1.389vw]">
|
||||
<div className="title-s mb-[0.833vw]">Скопировать ссылку</div>
|
||||
<p className="title-s mb-[0.833vw] font-medium">Скопировать ссылку</p>
|
||||
<LinkShare link={"https://estate.stream/ahdy12jdco1"} />
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="w-full flex items-center justify-center gap-[0.556vw]"
|
||||
>
|
||||
<Button variant="primary" size="large" className="w-full">
|
||||
Отправить
|
||||
<div className="size-[1.111vw]">
|
||||
<div className="2xl:size-[1.111vw] size-4">
|
||||
<ShareFilledIcon />
|
||||
</div>
|
||||
</Button>
|
||||
</>
|
||||
</PopupWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default SharePopup;
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function LinkShare({ link }: { link: string }) {
|
||||
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">
|
||||
<span
|
||||
className="text-ellipsis overflow-hidden text-s hover:cursor-pointer"
|
||||
className="text-ellipsis text-s hover:cursor-pointer overflow-hidden"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{link}
|
||||
@@ -30,7 +30,7 @@ export default function LinkShare({ link }: { link: string }) {
|
||||
<Button
|
||||
variant="cta"
|
||||
size="medium"
|
||||
className="h-[2.778vw] translate-x-[0.556vw]"
|
||||
className="translate-x-[0.556vw]"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
Копировать
|
||||
|
||||
+10
-10
@@ -12,23 +12,23 @@ body {
|
||||
|
||||
@layer utilities {
|
||||
.title-l {
|
||||
@apply text-2xl leading-[120%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[1.667vw] text-2xl leading-[120%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
.title-m {
|
||||
@apply text-xl leading-[110%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[1.389vw] text-xl leading-[110%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
.title-s {
|
||||
@apply text-base leading-[110%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[1.111vw] text-base leading-[110%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
.text-m {
|
||||
@apply text-base leading-[125%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[1.111vw] text-base leading-[125%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
.text-s {
|
||||
@apply text-sm leading-[115%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[0.972vw] text-sm leading-[115%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
/* .text-xs {
|
||||
@@ -36,22 +36,22 @@ body {
|
||||
} */
|
||||
|
||||
.button-m {
|
||||
@apply text-sm leading-[115%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[0.972vw] text-sm leading-[115%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
.button-s {
|
||||
@apply text-xs leading-[130%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[0.833vw] text-xs leading-[130%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
.caption-m {
|
||||
@apply text-sm leading-[120%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[0.972vw] text-sm leading-[120%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
.caption-s {
|
||||
@apply text-xs leading-[120%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[0.833vw] text-xs leading-[120%] tracking-[-0.02em];
|
||||
}
|
||||
|
||||
.caption-xs {
|
||||
@apply text-[10px] leading-[110%] tracking-[-0.02em];
|
||||
@apply 2xl:text-[0.729vw] text-[10px] leading-[110%] tracking-[-0.02em];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { queryClient } from "./lib/queryClient";
|
||||
import ProtectedRoute from "./components/ProtectedRoute";
|
||||
import PublicRoute from "./components/PublicRoute";
|
||||
import ModalContainer from "./components/ModalContainer";
|
||||
import PopupContainer from "./components/PopupContainer";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@@ -48,5 +50,7 @@ const router = createBrowserRouter([
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
<ModalContainer />
|
||||
<PopupContainer />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<<<<<<< HEAD
|
||||
import ChatPopup from "../components/popups/ChatPopup";
|
||||
=======
|
||||
>>>>>>> 8aef8a530bcdd53af4add911e773f2691e0027e4
|
||||
import Button from "../components/ui/Button";
|
||||
import FloatingActionButton from "../components/ui/FloatingActionButton";
|
||||
import { useMe, useLogout } from "../hooks/useAuth";
|
||||
import { useNavigate } from "react-router";
|
||||
import ShareFilledIcon from "../components/icons/ShareFilledIcon";
|
||||
import SharePopup from "../components/popups/SharePopup";
|
||||
import usePopupStore from "../store/popupStore";
|
||||
|
||||
function HomePage() {
|
||||
const { data: user } = useMe();
|
||||
@@ -13,12 +20,26 @@ function HomePage() {
|
||||
navigate("/login");
|
||||
};
|
||||
|
||||
const { setPopup } = usePopupStore();
|
||||
|
||||
return (
|
||||
<div className="py-8 min-h-screen bg-gray-50">
|
||||
<div className="px-4 mx-auto max-w-4xl">
|
||||
<div className="p-8 bg-white rounded-lg shadow-md">
|
||||
<h1 className="mb-6 text-3xl font-bold">Главная страница</h1>
|
||||
|
||||
{/* Потестить модалки */}
|
||||
|
||||
<FloatingActionButton
|
||||
variant="default"
|
||||
// onClick={() => setModal(<ShareModal />)}
|
||||
onClick={() => setPopup(<SharePopup />)}
|
||||
>
|
||||
<div className="2xl:size-[1.111vw] size-4 text-white">
|
||||
<ShareFilledIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<h2 className="mb-2 text-xl font-semibold">
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { create } from "zustand";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface ModalState {
|
||||
modal: ReactNode | null;
|
||||
setModal: (modal: ReactNode | null) => void;
|
||||
position: "center" | "right";
|
||||
setPosition: (position: "center" | "right") => void;
|
||||
}
|
||||
|
||||
const useModalStore = create<ModalState>()((set) => ({
|
||||
modal: null,
|
||||
position: "center",
|
||||
setPosition: (position) => set({ position }),
|
||||
setModal: (modal) => set({ modal }),
|
||||
}));
|
||||
|
||||
export default useModalStore;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { create } from "zustand";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface PopupState {
|
||||
popup: ReactNode | null;
|
||||
setPopup: (popup: ReactNode | null) => void;
|
||||
position: { x: number; y: number };
|
||||
setPosition: (position: { x: number; y: number }) => void;
|
||||
}
|
||||
|
||||
const usePopupStore = create<PopupState>()((set) => ({
|
||||
popup: null,
|
||||
position: { x: 0, y: 0 },
|
||||
setPosition: (position) => set({ position }),
|
||||
setPopup: (popup) => set({ popup }),
|
||||
}));
|
||||
|
||||
export default usePopupStore;
|
||||
Reference in New Issue
Block a user