From 2435aa2814802118027944b736c7f2c74a76b38d Mon Sep 17 00:00:00 2001 From: inmake Date: Wed, 30 Oct 2024 20:57:32 +0500 Subject: [PATCH] upd --- client/src/components/Button.tsx | 102 ++++++++----- client/src/components/Calendar.tsx | 8 +- client/src/components/Card.tsx | 10 +- client/src/components/DataField.tsx | 40 +++++ client/src/components/EmptyCard.tsx | 6 +- client/src/components/Header.tsx | 2 +- client/src/components/Input.tsx | 2 +- client/src/components/Managers.tsx | 25 ++- client/src/components/Menu.tsx | 28 +++- client/src/components/ModalContainer.tsx | 2 +- client/src/components/ModalTabButton.tsx | 19 +++ client/src/components/Schedule.tsx | 46 +++--- client/src/components/Schedules.tsx | 12 +- client/src/components/Select3.tsx | 73 +++++++++ client/src/components/TabButton.tsx | 8 +- client/src/components/Timeline.tsx | 2 +- client/src/components/icons/CheckIcon.tsx | 2 - .../src/components/icons/ChevronDownIcon.tsx | 8 +- .../src/components/icons/ChevronLeftIcon.tsx | 33 ++-- .../src/components/icons/ChevronRightIcon.tsx | 10 +- client/src/components/icons/ChevronUpIcon.tsx | 8 +- client/src/components/icons/CloseIcon.tsx | 11 +- client/src/components/icons/CopyIcon.tsx | 25 +++ client/src/components/icons/MoreIcon.tsx | 17 ++- client/src/components/icons/RotateIcon.tsx | 39 +++++ client/src/components/modals/CompanyModal.tsx | 143 ++++++++++++++++++ .../components/modals/CreateBuildModal.tsx | 6 +- .../components/modals/CreateCompanyModal.tsx | 6 +- .../components/modals/CreateScheduleModal.tsx | 17 +-- .../modals/CreateScheduledSessionModal.tsx | 6 +- .../src/components/modals/CreateUserModal.tsx | 10 +- .../src/components/modals/EditUserModal.tsx | 10 +- .../src/components/modals/SettingsModal.tsx | 124 +++++++++++++++ client/src/pages/AdminCompanyPage.tsx | 26 ++-- client/src/pages/AdminPage.tsx | 10 +- client/src/pages/DashboardPage.tsx | 126 ++------------- client/src/pages/LoginPage.tsx | 17 +-- client/src/pages/RegistrationPage.tsx | 6 +- client/src/types/ICompany.ts | 5 + client/src/types/IUser.ts | 2 + client/src/utils/api.ts | 3 - server/src/index.ts | 6 +- server/src/models/Company.ts | 19 +++ server/src/routes/changePassword.ts | 35 +++++ server/src/routes/companies.ts | 11 ++ server/src/routes/login.ts | 10 +- 46 files changed, 802 insertions(+), 334 deletions(-) create mode 100644 client/src/components/DataField.tsx create mode 100644 client/src/components/ModalTabButton.tsx create mode 100644 client/src/components/Select3.tsx create mode 100644 client/src/components/icons/CopyIcon.tsx create mode 100644 client/src/components/icons/RotateIcon.tsx create mode 100644 client/src/components/modals/CompanyModal.tsx create mode 100644 client/src/components/modals/SettingsModal.tsx create mode 100644 server/src/routes/changePassword.ts diff --git a/client/src/components/Button.tsx b/client/src/components/Button.tsx index 9b94fe7..0bf635c 100644 --- a/client/src/components/Button.tsx +++ b/client/src/components/Button.tsx @@ -1,53 +1,85 @@ -import SpinnerIcon from "./icons/SpinnerIcon"; +import RotateIcon from "./icons/RotateIcon"; -interface ButtonProps { - type?: "button" | "reset" | "submit"; - color?: "primary" | "secondary" | "tertiary"; - size?: "small" | "medium"; - disabled?: boolean; - loading?: boolean; +interface Props { + type?: "button" | "submit" | "reset"; + variant?: "primary" | "secondary" | "tertiary"; + size?: "large" | "medium" | "small" | "xsmall"; + roundedFull?: boolean; + widthFull?: boolean; + icon?: JSX.Element; onlyIcon?: boolean; - icon?: React.ReactNode; children?: React.ReactNode; className?: string; - handleClick?: () => void; + disabled?: boolean; + loading?: boolean; + onClick?: () => void; } +const variantClasses = { + primary: + "bg-[#49A1F5] text-white hover:bg-[#4190DB] disabled:bg-[#F2F2F2] disabled:text-[#CCCCCC]", + secondary: + "bg-[#F0F1F2] text-[#77828C] hover:bg-[#E6ECF2] hover:text-[#4C5359] disabled:bg-[#F2F2F2] disabled:text-[#CCCCCC]", + tertiary: + "bg-transparent text-[#77828C] hover:bg-[#E6ECF2] hover:text-[#4C5359] disabled:bg-transparent disabled:text-[#CCCCCC]", +}; + +const sizeClasses = { + large: "", + medium: "px-6 py-3 text-sm font-semibold leading-[16px] h-10", + small: "px-4 py-2 text-xs font-semibold h-8", + xsmall: "", +}; + +const onlyIconSizeClasses = { + large: "", + medium: "", + small: "p-1.5", + xsmall: "", +}; + +const iconClasses = { + large: "", + medium: "w-6 h-6", + small: "w-5 h-5", + xsmall: "", +}; + function Button({ type = "button", - color = "primary", + variant = "primary", size = "small", - disabled, - loading = false, - onlyIcon, + roundedFull = false, + widthFull = false, icon, + onlyIcon = false, children, - className, - handleClick, -}: ButtonProps) { + className = "", + disabled, + loading, + onClick, +}: Props) { return ( ); } diff --git a/client/src/components/Calendar.tsx b/client/src/components/Calendar.tsx index 7ddd861..2d8389d 100644 --- a/client/src/components/Calendar.tsx +++ b/client/src/components/Calendar.tsx @@ -38,13 +38,13 @@ function Calendar() { return (
-
+

Календарь

@@ -71,7 +71,7 @@ function Calendar() { {["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"].map((value, index) => (
{value}
diff --git a/client/src/components/Card.tsx b/client/src/components/Card.tsx index 2c5fd0e..35356f3 100644 --- a/client/src/components/Card.tsx +++ b/client/src/components/Card.tsx @@ -127,8 +127,8 @@ function Card({ ) : ( manager.id === user.id && ( @@ -136,8 +136,8 @@ function Card({ ) ) : ( @@ -152,7 +152,7 @@ function Card({ }/scheduled/${scheduledSessionId}?admin=true`} target="_blank" > -
+ ); +} + +export default DataField; diff --git a/client/src/components/EmptyCard.tsx b/client/src/components/EmptyCard.tsx index 478562e..e339bb3 100644 --- a/client/src/components/EmptyCard.tsx +++ b/client/src/components/EmptyCard.tsx @@ -18,10 +18,10 @@ function EmptyCard({ buildId, startAt, duration }: Props) {
{isAfter(startAt, new Date()) && (
@@ -29,32 +29,25 @@ function Managers() { {managers.map((manager) => (
- +
+ {user?.name.split(" ")[0][0]} + {user?.name.split(" ")[1][0]} +

{manager.name}

{user?.role === "admin" && (
))}
{user?.role === "admin" && ( - )} diff --git a/client/src/components/Menu.tsx b/client/src/components/Menu.tsx index 4caa0f3..78bf571 100644 --- a/client/src/components/Menu.tsx +++ b/client/src/components/Menu.tsx @@ -7,6 +7,9 @@ import WorkIcon from "./icons/WorkIcon"; import ExitIcon from "./icons/ExitIcon"; import useAuthStore from "../stores/useAuthStore"; import { useClickAway } from "@uidotdev/usehooks"; +import useModalStore from "../stores/useModalStore"; +import SettingsModal from "./modals/SettingsModal"; +import CompanyModal from "./modals/CompanyModal"; function Menu() { const [isShow, setIsShow] = useState(false); @@ -15,10 +18,22 @@ function Menu() { setIsShow(false); }); + const { setModal } = useModalStore(); + function logout() { setUser(null); } + function handleClickSettings() { + setIsShow(false); + setModal(); + } + + function handleClickCompany() { + setIsShow(false); + setModal(); + } + return (
@@ -36,8 +51,8 @@ function Menu() { {(state) => ( -
-
+
+

- {user?.name[0]} + {user?.name.split(" ")[0][0]} + {user?.name.split(" ")[1][0]}

{user?.name}

-

{user?.role}

+

{user?.username}

@@ -76,8 +92,8 @@ function Menu() { Уведомления
+ ); +} + +export default ModalTabButton; diff --git a/client/src/components/Schedule.tsx b/client/src/components/Schedule.tsx index ba35999..a048a28 100644 --- a/client/src/components/Schedule.tsx +++ b/client/src/components/Schedule.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { ChangeEvent, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import Timeline from "./Timeline"; import { format, getDate, setDate, setHours, startOfDay } from "date-fns"; // import useEventStore from "../stores/useEventStore"; @@ -10,8 +10,9 @@ import Input from "./Input"; import Label from "./Label"; import api from "../utils/api"; import useStore from "../stores/useStore"; -import Select2 from "./Select2"; import IScheduledSession from "../types/IScheduledSession"; +import toast from "react-hot-toast"; +import Select3 from "./Select3"; interface Props { selectedDay: Date; @@ -48,9 +49,9 @@ function Schedule({ selectedDay, slots, events }: Props) { } async function handleClickSave() { - console.log("slot", slot); - console.log("startAt", startAt); - console.log("duration", duration); + // console.log("slot", slot); + // console.log("startAt", startAt); + // console.log("duration", duration); if (!slot || !startAt || !duration) return; @@ -97,7 +98,8 @@ function Schedule({ selectedDay, slots, events }: Props) { window.open(`${result.url}?admin=true`); } catch (error) { - console.log(error); + // console.log(error); + toast.error((error as Error).message); } // setIsLoading(false); @@ -108,14 +110,8 @@ function Schedule({ selectedDay, slots, events }: Props) { if (!builds) return; setSelectedBuild(builds[0]); - - console.log(builds); }, [builds]); - useEffect(() => { - console.log("events", events); - }, [events]); - useEffect(() => { if (!dateForInstantStart) return; @@ -199,10 +195,10 @@ function Schedule({ selectedDay, slots, events }: Props) { : "Запланировать демонстрацию"}

@@ -210,7 +206,7 @@ function Schedule({ selectedDay, slots, events }: Props) {

Выберите ЖК

- ({ value: build.build, @@ -224,6 +220,18 @@ function Schedule({ selectedDay, slots, events }: Props) { )! ) } + /> */} + build.name)?.name + } + options={builds.map((build) => build.name)} + onSelect={(option) => + setSelectedBuild( + builds.find((build) => build.name === option)! + ) + } />
@@ -275,13 +283,13 @@ function Schedule({ selectedDay, slots, events }: Props) { )}
- @@ -292,7 +300,7 @@ function Schedule({ selectedDay, slots, events }: Props) {
diff --git a/client/src/components/Schedules.tsx b/client/src/components/Schedules.tsx index 67e0eb2..45938f8 100644 --- a/client/src/components/Schedules.tsx +++ b/client/src/components/Schedules.tsx @@ -16,13 +16,13 @@ function Schedules() { return (
-
+

Расписание

@@ -30,7 +30,7 @@ function Schedules() {
{schedules?.map((schedule) => (
-

+

Действует с{" "} {format(new Date(schedule.startDate), "dd.MM.yyyy")} @@ -68,9 +68,9 @@ function Schedules() { {user?.role === "admin" && ( diff --git a/client/src/components/Select3.tsx b/client/src/components/Select3.tsx new file mode 100644 index 0000000..2ab6e07 --- /dev/null +++ b/client/src/components/Select3.tsx @@ -0,0 +1,73 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { useEffect, useState } from "react"; +import ChevronDownIcon from "./icons/ChevronDownIcon"; +import { useClickAway } from "@uidotdev/usehooks"; +import CheckIcon from "./icons/CheckIcon"; + +interface Props { + defaultOption?: string; + options: string[]; + onSelect: (option: string) => void; +} + +function Select3({ defaultOption, options, onSelect }: Props) { + const [showOptions, setShowOptions] = useState(false); + const [selectedOption, setSelectedOption] = useState(); + + const ref = useClickAway(() => { + setShowOptions(false); + }); + + function handleSelect(option: string) { + setSelectedOption(option); + onSelect(option); + setShowOptions(false); + } + + useEffect(() => { + if (!defaultOption) return; + + handleSelect(defaultOption); + }, [defaultOption]); + + return ( +

+
setShowOptions((prev) => !prev)} + > + +
+
+ +
+
+
+ {showOptions && ( +
+ {options.map((option, index) => ( + + ))} +
+ )} +
+ ); +} + +export default Select3; diff --git a/client/src/components/TabButton.tsx b/client/src/components/TabButton.tsx index afe98e2..af8d97e 100644 --- a/client/src/components/TabButton.tsx +++ b/client/src/components/TabButton.tsx @@ -2,21 +2,21 @@ interface TabButtonProps { children: React.ReactNode; active?: boolean; className?: string; - handleClick?: () => void; + onClick?: () => void; } function TabButton({ children, active = false, className, - handleClick, + onClick, }: TabButtonProps) { return ( diff --git a/client/src/components/Timeline.tsx b/client/src/components/Timeline.tsx index 7f82078..451e11b 100644 --- a/client/src/components/Timeline.tsx +++ b/client/src/components/Timeline.tsx @@ -119,7 +119,7 @@ function Timeline({ setStartAt(undefined); setDuration(0); ref.current.style.height = "0px"; - console.log(ref.current.clientHeight); + // console.log(ref.current.clientHeight); }, [draftMode]); return ( diff --git a/client/src/components/icons/CheckIcon.tsx b/client/src/components/icons/CheckIcon.tsx index 8477d31..87052b0 100644 --- a/client/src/components/icons/CheckIcon.tsx +++ b/client/src/components/icons/CheckIcon.tsx @@ -1,8 +1,6 @@ function CheckIcon() { return ( + ( - - - -); -export default SVGComponent; +function ChevronLeftIcon() { + return ( + + + + ); +} + +export default ChevronLeftIcon; diff --git a/client/src/components/icons/ChevronRightIcon.tsx b/client/src/components/icons/ChevronRightIcon.tsx index aa88603..9ba2422 100644 --- a/client/src/components/icons/ChevronRightIcon.tsx +++ b/client/src/components/icons/ChevronRightIcon.tsx @@ -1,16 +1,10 @@ function ChevronRightIcon() { return ( - + diff --git a/client/src/components/icons/ChevronUpIcon.tsx b/client/src/components/icons/ChevronUpIcon.tsx index 10bb66b..47d8493 100644 --- a/client/src/components/icons/ChevronUpIcon.tsx +++ b/client/src/components/icons/ChevronUpIcon.tsx @@ -1,12 +1,6 @@ function ChevronUpIcon() { return ( - + diff --git a/client/src/components/icons/CopyIcon.tsx b/client/src/components/icons/CopyIcon.tsx new file mode 100644 index 0000000..0af133d --- /dev/null +++ b/client/src/components/icons/CopyIcon.tsx @@ -0,0 +1,25 @@ +interface Props { + className?: string; +} + +function CopyIcon({ className = "" }: Props) { + return ( + + + + ); +} + +export default CopyIcon; diff --git a/client/src/components/icons/MoreIcon.tsx b/client/src/components/icons/MoreIcon.tsx index ec25d90..8e4e37b 100644 --- a/client/src/components/icons/MoreIcon.tsx +++ b/client/src/components/icons/MoreIcon.tsx @@ -1,15 +1,20 @@ -function MoreIcon() { +interface Props { + className?: string; +} + +function MoreIcon({ className = "" }: Props) { return ( - - - + + + ); } diff --git a/client/src/components/icons/RotateIcon.tsx b/client/src/components/icons/RotateIcon.tsx new file mode 100644 index 0000000..711500e --- /dev/null +++ b/client/src/components/icons/RotateIcon.tsx @@ -0,0 +1,39 @@ +interface Props { + className?: string; +} + +function RotateIcon({ className = "" }: Props) { + return ( + + + + + + + + + + + + + + + + ); +} + +export default RotateIcon; diff --git a/client/src/components/modals/CompanyModal.tsx b/client/src/components/modals/CompanyModal.tsx new file mode 100644 index 0000000..e0608fa --- /dev/null +++ b/client/src/components/modals/CompanyModal.tsx @@ -0,0 +1,143 @@ +import { useState } from "react"; +import Button from "../Button"; +import CloseIcon from "../icons/CloseIcon"; +import useModalStore from "../../stores/useModalStore"; +import DataField from "../DataField"; +import useAuthStore from "../../stores/useAuthStore"; +import MoreIcon from "../icons/MoreIcon"; +import useStore from "../../stores/useStore"; +import ModalTabButton from "../ModalTabButton"; + +function CompanyModal() { + const [selectedTab, setSelectedTab] = useState<"company" | "employees">( + "company" + ); + const { user } = useAuthStore(); + const { setModal } = useModalStore(); + const { company, managers } = useStore(); + + return ( +
+
+
+ setSelectedTab("company")} + > + Компания + + setSelectedTab("employees")} + > + Сотрудники + +
+
+
+
+ {selectedTab === "company" && ( +
+
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ {/*
+
+

Проекты

+ {Array.from({ length: 3 }).map((_, index) => ( +
+
+

+
+ ))} +
+
+
*/} +
+ )} + {selectedTab === "employees" && ( + <> +
+
+

Имя

+
+
+

Должность

+
+
+

Телефон

+
+
+
+
+ {managers.map((manager, index) => ( +
+
+
+
+

+ {user?.name.split(" ")[0][0]} + {user?.name.split(" ")[1][0]} +

+
+
+

{manager.name}

+

+ {manager.username} +

+
+
+
+
+

{manager.position}

+
+
+

{manager.phone}

+
+
+ {user?.role === "admin" && ( +
+
+ ))} +
+
+ +
+ + )} +
+ ); +} + +export default CompanyModal; diff --git a/client/src/components/modals/CreateBuildModal.tsx b/client/src/components/modals/CreateBuildModal.tsx index 9c90b9e..bf3abd1 100644 --- a/client/src/components/modals/CreateBuildModal.tsx +++ b/client/src/components/modals/CreateBuildModal.tsx @@ -47,7 +47,7 @@ function CreateBuildModal({ companyId }: Props) { } return ( -
+

Создание ЖК

setSessionLimit(+value)} required /> -
- diff --git a/client/src/components/modals/CreateCompanyModal.tsx b/client/src/components/modals/CreateCompanyModal.tsx index f80f003..b006925 100644 --- a/client/src/components/modals/CreateCompanyModal.tsx +++ b/client/src/components/modals/CreateCompanyModal.tsx @@ -38,7 +38,7 @@ function CreateCompanyModal() { } return ( -
+

Создание компании

-
- diff --git a/client/src/components/modals/CreateScheduleModal.tsx b/client/src/components/modals/CreateScheduleModal.tsx index 8926346..dc66732 100644 --- a/client/src/components/modals/CreateScheduleModal.tsx +++ b/client/src/components/modals/CreateScheduleModal.tsx @@ -114,11 +114,10 @@ function CreateScheduleModal() {

Создание расписания

@@ -265,7 +264,7 @@ function CreateScheduleModal() { {/*

Статистика

*/} -
+

Предварительный просмотр

@@ -291,7 +290,7 @@ function CreateScheduleModal() {
{/*

Выходные дни

-

+

{weekends.map((value) => ( {value} ))} @@ -303,9 +302,9 @@ function CreateScheduleModal() {

-
- - +
diff --git a/client/src/components/modals/CreateScheduledSessionModal.tsx b/client/src/components/modals/CreateScheduledSessionModal.tsx index 99a88c9..fcd3586 100644 --- a/client/src/components/modals/CreateScheduledSessionModal.tsx +++ b/client/src/components/modals/CreateScheduledSessionModal.tsx @@ -58,10 +58,10 @@ function CreateScheduledSessionModal({ buildId, startAt, duration }: Props) {

Запланировать демонстрацию

@@ -137,7 +137,7 @@ function CreateScheduledSessionModal({ buildId, startAt, duration }: Props) { -
diff --git a/client/src/components/modals/CreateUserModal.tsx b/client/src/components/modals/CreateUserModal.tsx index 360522d..1410990 100644 --- a/client/src/components/modals/CreateUserModal.tsx +++ b/client/src/components/modals/CreateUserModal.tsx @@ -93,7 +93,7 @@ function CreateUserModal({ companyId }: Props) { }, [builds]); return ( -
+

Создание пользователя

-
- diff --git a/client/src/components/modals/EditUserModal.tsx b/client/src/components/modals/EditUserModal.tsx index aeb334e..79e882f 100644 --- a/client/src/components/modals/EditUserModal.tsx +++ b/client/src/components/modals/EditUserModal.tsx @@ -119,7 +119,7 @@ function EditUserModal({ companyId, userId }: Props) { }, [user, builds, buildIds]); return ( -
+

Редактирование

-
- diff --git a/client/src/components/modals/SettingsModal.tsx b/client/src/components/modals/SettingsModal.tsx new file mode 100644 index 0000000..018cc45 --- /dev/null +++ b/client/src/components/modals/SettingsModal.tsx @@ -0,0 +1,124 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { FormEvent, useState } from "react"; +import useAuthStore from "../../stores/useAuthStore"; +import useModalStore from "../../stores/useModalStore"; +import Button from "../Button"; +import CloseIcon from "../icons/CloseIcon"; +import Input from "../Input"; +import Label from "../Label"; +import api from "../../utils/api"; +import toast from "react-hot-toast"; +import DataField from "../DataField"; + +function SettingsModal() { + const { setModal } = useModalStore(); + const { user } = useAuthStore(); + const [oldPassword, setOldPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [loading, setLoading] = useState(false); + + async function handleSubmitChangePassword(e: FormEvent) { + e.preventDefault(); + + setLoading(true); + await changePassword(); + setLoading(false); + } + + async function changePassword() { + try { + const result: any = await api + .post("changePassword", { + json: { + oldPassword, + newPassword, + }, + }) + .json(); + + if ("error" in result) { + toast.error(result.error); + return; + } + + toast.success("Пароль успешно изменен"); + } catch (error) { + toast.error((error as Error).message); + } + } + + return ( +
+
+
+

Профиль

+
+
+
+
+

+ {user?.name.split(" ")[0][0]} + {user?.name.split(" ")[1][0]} +

+
+
+ + + {/* */} + +
+ + {/* */} +
+
+
+ {/*
*/} +
+
+

Безопасность

+
+ +
+
+
+
+ + +
+
+ ); +} + +export default SettingsModal; diff --git a/client/src/pages/AdminCompanyPage.tsx b/client/src/pages/AdminCompanyPage.tsx index 3943c72..75fb47f 100644 --- a/client/src/pages/AdminCompanyPage.tsx +++ b/client/src/pages/AdminCompanyPage.tsx @@ -75,13 +75,13 @@ function AdminCompanyPage() { }, [users, builds]); return ( -
+
@@ -90,23 +90,23 @@ function AdminCompanyPage() {

Жилые комплексы

-
+
{builds && builds.map((build) => ( -
+
-
+

{build.name}

Сборка приложения:{" "} @@ -121,11 +121,11 @@ function AdminCompanyPage() { ))}

-
+

Пользователи

@@ -57,15 +57,15 @@ function AdminPage() { {companies && companies.map((company) => ( diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index 5adf733..81bf1a0 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -17,13 +17,13 @@ import IUser from "../types/IUser"; import IError from "../types/IError"; import Schedule from "../components/Schedule"; import IScheduledSession from "../types/IScheduledSession"; +import toast, { Toaster } from "react-hot-toast"; function DashboardPage() { const { user } = useAuthStore(); const { company, setCompany, - selectedBuild, builds, setBuilds, managers, @@ -55,7 +55,7 @@ function DashboardPage() { async function getCompany() { if (!user) { - console.log("No User", user); + // console.log("No User", user); return; } @@ -64,15 +64,13 @@ function DashboardPage() { setCompany(result); } catch (error) { - if (error instanceof Error) { - console.log("Error: ", error.message); - } + toast.error((error as Error).message); } } async function getBuilds() { if (!user) { - console.log("No User", user); + // console.log("No User", user); return; } @@ -83,20 +81,18 @@ function DashboardPage() { setBuilds(result); } catch (error) { - if (error instanceof Error) { - console.log("Error: ", error.message); - } + toast.error((error as Error).message); } } async function getManagers() { - if (!company || !selectedBuild) { + if (!company) { return; } try { const result: IUser[] | IError = await api - .get(`users?companyId=${company.id}&buildIds=${selectedBuild.id}`) + .get(`companies/${company.id}/users`) .json(); if ("error" in result) { @@ -104,8 +100,6 @@ function DashboardPage() { return; } - console.log("result", result); - setManagers(result); } catch (error) { alert((error as Error).message); @@ -114,7 +108,7 @@ function DashboardPage() { async function getScheduledSessions(useLoader?: boolean) { if (!company) { - console.log("No ScheduledSessions"); + // console.log("No ScheduledSessions"); return; } @@ -129,13 +123,9 @@ function DashboardPage() { ) .json(); - console.log(result); - setScheduledSessions(result); } catch (error) { - if (error instanceof Error) { - console.log("Error: ", error.message); - } + toast.error((error as Error).message); } if (useLoader) setIsLoadingScheduledSessions(false); @@ -200,110 +190,23 @@ function DashboardPage() {

- +
- {/*
-
- {currentTime} -
- - {selectedBuild && - [...Array(selectedBuild.sessionLimit)].map((_, index) => ( -
-
- Слот {index + 1} -
-
- ))} -
*/} - - {/*
- - {(state) => ( -
- -
- )} -
- - {generatedScheduledSessions?.map( - (generatedScheduledSession, index) => ( -
-
-

{format(generatedScheduledSession.time, "HH:mm")}

-
-
- {company && - managers && - selectedBuild && - generatedScheduledSession.sessions.map( - (session: any, index2: number) => { - const selectedManager = managers.find( - (manager) => manager.id == session.userId - ); - - if (!_.isEmpty(session)) { - return ( - - updateScheduledSessionManager( - scheduledSessionId, - managerId - ) - } - /> - ); - } else { - return ( - - ); - } - } - )} -
-
- ) - )} -
*/} - {company && scheduledSessions && ( +
); } diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx index 0a150dc..4747226 100644 --- a/client/src/pages/LoginPage.tsx +++ b/client/src/pages/LoginPage.tsx @@ -7,7 +7,7 @@ import Input from "../components/Input"; import Button from "../components/Button"; import { Link, useNavigate } from "react-router-dom"; import useAuthStore from "../stores/useAuthStore"; -import toast from "react-hot-toast"; +import toast, { Toaster } from "react-hot-toast"; import IError from "../types/IError"; import IUser from "../types/IUser"; import api from "../utils/api"; @@ -32,19 +32,11 @@ function LoginPage() { if ("error" in result) { setLoading(false); - toast.error(`Error: ${result.error}`); + toast.error(result.error); return; } - setUser({ - id: result.id, - username: "username", - accessToken: result.accessToken, - companyId: result.companyId, - name: result.name, - role: result.role, - buildIds: result.buildIds, - }); + setUser(result); navigate("/dashboard"); } catch (error) { @@ -88,6 +80,7 @@ function LoginPage() { size="medium" className="mt-10" loading={loading} + widthFull > Войти @@ -102,6 +95,8 @@ function LoginPage() {
+ +
); } diff --git a/client/src/pages/RegistrationPage.tsx b/client/src/pages/RegistrationPage.tsx index 20ac518..c65f66f 100644 --- a/client/src/pages/RegistrationPage.tsx +++ b/client/src/pages/RegistrationPage.tsx @@ -3,8 +3,8 @@ import Button from "../components/Button"; function RegistrationPage() { return ( -
-
+
+

Получение данных для входа

@@ -22,7 +22,7 @@ function RegistrationPage() {
- diff --git a/client/src/types/ICompany.ts b/client/src/types/ICompany.ts index 8bb2cf4..93c0095 100644 --- a/client/src/types/ICompany.ts +++ b/client/src/types/ICompany.ts @@ -2,6 +2,11 @@ interface ICompany { id: string; name: string; sessionLimit: number; + site?: string; + email?: string; + phone?: string; + address?: string; + avatar?: string; } export default ICompany; diff --git a/client/src/types/IUser.ts b/client/src/types/IUser.ts index 47e3894..96d9ee7 100644 --- a/client/src/types/IUser.ts +++ b/client/src/types/IUser.ts @@ -7,6 +7,8 @@ interface IUser { name: string; role: string; buildIds?: string[]; + position?: string; + phone?: string; } export default IUser; diff --git a/client/src/utils/api.ts b/client/src/utils/api.ts index ec83f0d..216c501 100644 --- a/client/src/utils/api.ts +++ b/client/src/utils/api.ts @@ -2,7 +2,6 @@ import ky from "ky"; import useAuthStore from "../stores/useAuthStore"; import IError from "../types/IError"; -import toast from "react-hot-toast"; const api = ky.extend({ prefixUrl: import.meta.env.VITE_API_URL, @@ -38,8 +37,6 @@ const api = ky.extend({ } catch (error) { window.location.href = "/login"; } - } else { - toast.error(response.statusText); } }, ], diff --git a/server/src/index.ts b/server/src/index.ts index 69d8415..ab5b960 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -17,6 +17,7 @@ import scheduledSessionsRoute from "./routes/scheduledSessions.js"; import adminCompaniesRoute from "./routes/admin/adminCompaniesRoute.js"; import adminBuildsRoute from "./routes/admin/adminBuildsRoute.js"; import adminUsersRoute from "./routes/admin/adminUsersRoute.js"; +import changePasswordRoute from "./routes/changePassword.js"; await connectDB(); @@ -42,11 +43,12 @@ app.use("/actions", actionsRouter); app.use("/builds", buildsRouter); app.use("/scheduled_sessions", scheduledSessionsRoute); app.use("/schedules", schedulesRouter); -app.use("/companies", authMiddleware, companiesRouter); -app.use("/users", authMiddleware, usersRouter); app.use("/admin/companies", adminCompaniesRoute); app.use("/admin/builds", adminBuildsRoute); app.use("/admin/users", adminUsersRoute); +app.use("/companies", authMiddleware, companiesRouter); +app.use("/users", authMiddleware, usersRouter); +app.use("/changePassword", authMiddleware, changePasswordRoute); app.listen(port, () => { console.log(`Server listening on port ${port}`); diff --git a/server/src/models/Company.ts b/server/src/models/Company.ts index 487452f..2baf423 100644 --- a/server/src/models/Company.ts +++ b/server/src/models/Company.ts @@ -7,6 +7,25 @@ const companySchema = new Schema( required: true, unique: true, }, + sessionLimit: { + required: true, + type: Number, + }, + avatar: { + type: String, + }, + phone: { + type: String, + }, + site: { + type: String, + }, + email: { + type: String, + }, + address: { + type: String, + }, }, { timestamps: true, diff --git a/server/src/routes/changePassword.ts b/server/src/routes/changePassword.ts new file mode 100644 index 0000000..2c9b693 --- /dev/null +++ b/server/src/routes/changePassword.ts @@ -0,0 +1,35 @@ +import bcrypt from "bcrypt"; +import { Router } from "express"; +import User from "../models/User.js"; +import Token from "../models/Token.js"; + +const router = Router(); + +router.post("/", async (req, res) => { + const { oldPassword, newPassword } = req.body; + + try { + const user = res.locals.user; + const accessToken = res.locals.accessToken; + + if (!bcrypt.compareSync(oldPassword, user.password)) { + return res.json({ error: "Old password is wrong" }); + } + + const newPasswordHash = bcrypt.hashSync(newPassword, 12); + + await User.findByIdAndUpdate(user._id, { password: newPasswordHash }); + await Token.deleteMany({ + userId: user._id, + accessToken: { $ne: accessToken }, + }); + + return res.json({ ok: 1 }); + } catch (error) { + return res.json({ error: (error as Error).message }); + } +}); + +const changePasswordRoute = router; + +export default changePasswordRoute; diff --git a/server/src/routes/companies.ts b/server/src/routes/companies.ts index 55a6642..236e7d1 100644 --- a/server/src/routes/companies.ts +++ b/server/src/routes/companies.ts @@ -285,6 +285,17 @@ router.get( } ); +router.get("/:companyId/users", async (req, res) => { + try { + const companyId = req.params.companyId; + const users = await User.find({ companyId }); + + return res.json(users); + } catch (error) { + return res.json({ error: (error as Error).message }); + } +}); + const companiesRouter = router; export default companiesRouter; diff --git a/server/src/routes/login.ts b/server/src/routes/login.ts index dc0981c..a3ec23f 100644 --- a/server/src/routes/login.ts +++ b/server/src/routes/login.ts @@ -11,17 +11,17 @@ router.post("/", async (req, res) => { const { username, password } = req.body; if (!username || !password) { - return res.json({ error: 1 }); + return res.json({ error: "Неверный логин или пароль" }); } const user = await User.findOne({ username }).lean(); if (!user) { - return res.json({ error: 2 }); + return res.json({ error: "Неверный логин или пароль" }); } if (!bcrypt.compareSync(password, user.password!)) { - return res.json({ error: 3 }); + return res.json({ error: "Неверный логин или пароль" }); } const accessToken = await new SignJWT({ username }) @@ -36,12 +36,14 @@ router.post("/", async (req, res) => { await Token.create({ userId: user._id, accessToken, refreshToken }); + const userWithoutPassword = { ...user, password: undefined }; + res .cookie("refreshToken", refreshToken, { httpOnly: true, expires: new Date(decodeJwt(refreshToken).exp! * 1000), }) - .json({ accessToken, refreshToken, ...user }); + .json({ accessToken, refreshToken, ...userWithoutPassword }); }); const loginRoute = router;