upd
This commit is contained in:
@@ -1,19 +1,22 @@
|
||||
import clsx from "clsx";
|
||||
import SpinIcon from "./icons/SpinIcon";
|
||||
|
||||
interface NewInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
placeholder?: string;
|
||||
isError?: boolean;
|
||||
errorMessage?: string;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
function Input({
|
||||
placeholder,
|
||||
isError,
|
||||
errorMessage,
|
||||
isLoading,
|
||||
...props
|
||||
}: NewInputProps) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className={clsx("relative", props.disabled && "opacity-40")}>
|
||||
<input
|
||||
{...props}
|
||||
placeholder=""
|
||||
@@ -30,7 +33,7 @@ function Input({
|
||||
peer-focus:caption-xs peer-focus:top-[0.556vw] peer-focus:translate-y-0
|
||||
peer-[:not(:placeholder-shown)]:caption-xs peer-[:not(:placeholder-shown)]:top-[0.556vw] peer-[:not(:placeholder-shown)]:translate-y-0"
|
||||
>
|
||||
{placeholder}
|
||||
{placeholder + (props.required ? "*" : "")}
|
||||
</span>
|
||||
)}
|
||||
{isError && (
|
||||
@@ -38,6 +41,11 @@ function Input({
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="size-[1.389vw] text-[#7B60F3] animate-spin z-1 absolute right-[1.111vw] top-1/2 -translate-y-1/2">
|
||||
<SpinIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,17 +29,15 @@ function MultySelect<T extends { name: string; id: string }>({
|
||||
setIsSelectVisible(false);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
onSelect(selectedValues);
|
||||
}, [selectedValues]);
|
||||
useEffect(() => onSelect(selectedValues), [selectedValues]);
|
||||
|
||||
const handleSelectClick = (item: T) => {
|
||||
const isItemSelected = selectedValues.some((val) => val.id === item.id);
|
||||
|
||||
if (isItemSelected) {
|
||||
setSelectedValues(selectedValues.filter((value) => value.id !== item.id));
|
||||
setSelectedValues((prev) => prev.filter((value) => value.id !== item.id));
|
||||
} else {
|
||||
setSelectedValues([...selectedValues, item]);
|
||||
setSelectedValues((prev) => [...prev, item]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,9 +49,7 @@ function MultySelect<T extends { name: string; id: string }>({
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center justify-between px-[1.111vw] py-[1.285vw] hover:bg-[#F0F0F0] rounded-[0.833vw] cursor-pointer",
|
||||
isSelectVisible
|
||||
? "!bg-[#E1DEFC] !text-[#7B60F3] hover:bg-[#E1DEFC]"
|
||||
: "text-[#141414]"
|
||||
isSelectVisible && "!bg-[#E1DEFC] !text-[#7B60F3] hover:bg-[#E1DEFC]"
|
||||
)}
|
||||
onClick={() => setIsSelectVisible(!isSelectVisible)}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { IApp } from "../types/App";
|
||||
import { App } from "../types/App";
|
||||
import ChevronLeftIcon from "./icons/ChevronLeftIcon";
|
||||
import CloseIcon from "./icons/CloseIcon";
|
||||
import LightningIcon from "./icons/LightningIcon";
|
||||
@@ -8,10 +8,10 @@ import CheckIcon from "./icons/CheckIcon";
|
||||
import ChevronDownIcon from "./icons/ChevronDownIcon";
|
||||
|
||||
interface Props {
|
||||
projects: IApp[];
|
||||
selectedProject: IApp | null;
|
||||
setSelectedProject: (project: IApp | null) => void;
|
||||
activeProject: IApp | null;
|
||||
projects: App[];
|
||||
selectedProject: App | null;
|
||||
setSelectedProject: (project: App | null) => void;
|
||||
activeProject: App | null;
|
||||
}
|
||||
|
||||
function ProjectSelector({
|
||||
@@ -26,7 +26,7 @@ function ProjectSelector({
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [pointedProject, setPointedProject] = useState<IApp | null>(null);
|
||||
const [pointedProject, setPointedProject] = useState<App | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setPointedProject(selectedProject);
|
||||
|
||||
@@ -15,8 +15,6 @@ function SpinIcon() {
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
background:
|
||||
"conic-gradient(from 90deg,rgba(255,255,255,.0195) 0deg,#fff 314.826deg,rgba(255,255,255,0) 353.741deg,rgba(255,255,255,.0195) 360deg)",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
opacity: 1,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Server } from "../../types/Server.ts";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { IApp } from "../../types/App.ts";
|
||||
import { App } from "../../types/App.ts";
|
||||
import api from "../../utils/api.ts";
|
||||
import { Session } from "../../types/Session.ts";
|
||||
import { Client } from "../../types/Client.ts";
|
||||
@@ -11,6 +11,8 @@ import StartSessionIcon from "../icons/StartSessionIcon.tsx";
|
||||
import Button from "../Button.tsx";
|
||||
import ProjectSelector from "../ProjectSelector.tsx";
|
||||
import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useDebounce } from "@uidotdev/usehooks";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
|
||||
interface Props {
|
||||
targetServerId: string | null;
|
||||
@@ -38,7 +40,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
const [selectedServer, setSelectedServer] = useState<Server | null>(
|
||||
targetServer
|
||||
);
|
||||
const [selectedApp, setSelectedApp] = useState<IApp | null>(null);
|
||||
const [selectedApp, setSelectedApp] = useState<App | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedApp(
|
||||
@@ -48,9 +50,32 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
);
|
||||
}, [selectedServer]);
|
||||
|
||||
const debouncedPhone = useDebounce(phone, 500);
|
||||
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ["get-user-by-phone", debouncedPhone],
|
||||
queryFn: () =>
|
||||
api
|
||||
.get("clients/by-phone", {
|
||||
searchParams: debouncedPhone ? { phone: debouncedPhone } : {},
|
||||
})
|
||||
.json<Client>(),
|
||||
enabled: !!debouncedPhone,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!error && data) {
|
||||
setName(data.name);
|
||||
setEmail(data.email);
|
||||
} else {
|
||||
setName(null);
|
||||
setEmail(null);
|
||||
}
|
||||
}, [data, error]);
|
||||
|
||||
const { mutate: createClient } = useMutation({
|
||||
mutationFn: () => {
|
||||
return api
|
||||
mutationFn: () =>
|
||||
api
|
||||
.post("clients", {
|
||||
json: {
|
||||
name,
|
||||
@@ -58,8 +83,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
email,
|
||||
},
|
||||
})
|
||||
.json<Client>();
|
||||
},
|
||||
.json<Client>(),
|
||||
});
|
||||
|
||||
const { mutate: createSession } = useMutation({
|
||||
@@ -158,7 +182,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
|
||||
return (
|
||||
<form
|
||||
className="relative rounded-[2.222vw] w-[25vw] min-h-[calc(100dvh-2.222vw)] bg-[#F0F0F0] flex flex-col overflow-hidden"
|
||||
className="relative rounded-[2.222vw] w-[25vw] bg-[#F0F0F0] flex flex-col overflow-hidden"
|
||||
onSubmit={handleClickCreateSession}
|
||||
ref={ref}
|
||||
>
|
||||
@@ -166,7 +190,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
<p className="title-s font-medium">Новый сеанс</p>
|
||||
</div>
|
||||
<div className="w-full h-[6.944vw] bg-[url(/images/Table.png)] bg-no-repeat bg-top bg-[length:9.306vw]" />
|
||||
<div className="bg-white rounded-t-[2.222vw] p-[1.389vw] flex flex-col gap-y-[1.667vw] flex-1 overflow-y-auto">
|
||||
<div className="bg-white rounded-t-[2.222vw] p-[1.389vw] flex flex-col gap-y-[1.667vw] flex-1moverflow-y-auto">
|
||||
<TableSelector
|
||||
tables={servers || []}
|
||||
selectedTable={selectedServer}
|
||||
@@ -180,19 +204,40 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="Номер телефона"
|
||||
required
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<Input
|
||||
value={name || ""}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Имя"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="email"
|
||||
value={email || ""}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Электронная почта"
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{phone && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<Input
|
||||
value={name || ""}
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Имя"
|
||||
required
|
||||
/>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<Input
|
||||
disabled={isLoading}
|
||||
type="email"
|
||||
value={email || ""}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Электронная почта"
|
||||
/>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-[0.833vw]">
|
||||
@@ -232,7 +277,8 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={
|
||||
!ref.current?.checkValidity() ||
|
||||
!phone ||
|
||||
!name ||
|
||||
!selectedServer ||
|
||||
!selectedApp ||
|
||||
servers?.find((server) => server.id === selectedServer?.id)
|
||||
@@ -240,7 +286,6 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
}
|
||||
variant="cta"
|
||||
size="large"
|
||||
className="sticky bottom-0"
|
||||
>
|
||||
<div className="size-[1.111vw]">
|
||||
<StartSessionIcon />
|
||||
|
||||
Reference in New Issue
Block a user