diff --git a/src/api/auth.ts b/src/api/auth.ts index 717cfa4..6219adb 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,27 +1,15 @@ -import { User } from "@root/model/auth"; import { authStore } from "@root/stores/makeRequest"; -const apiUrl = process.env.NODE_ENV === "production" ? "/user" : "https://hub.pena.digital/user"; -const authUrl = process.env.NODE_ENV === "production" ? "/auth" : "https://hub.pena.digital/auth"; +const apiUrl = process.env.NODE_ENV === "production" ? "" : "https://hub.pena.digital"; const makeRequest = authStore.getState().makeRequest; -export async function getUser(userId: string): Promise { - return makeRequest({ - url: `${apiUrl}/${userId}`, - contentType: true, - method: "GET", - useToken: false, - withCredentials: false, - }); -} - export function logout() { return makeRequest({ - url: authUrl + "/logout", + url: apiUrl + "/auth/logout", method: "POST", - useToken: false, + useToken: true, withCredentials: true, }); } \ No newline at end of file diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..1161d33 --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,28 @@ +import { PatchUserRequest, User } from "@root/model/user"; +import { authStore } from "@root/stores/makeRequest"; + + +const apiUrl = process.env.NODE_ENV === "production" ? "" : "https://hub.pena.digital"; + +const makeRequest = authStore.getState().makeRequest; + +export function getUser(userId: string): Promise { + return makeRequest({ + url: `${apiUrl}/user/${userId}`, + contentType: true, + method: "GET", + useToken: false, + withCredentials: false, + }); +} + +export function patchUser(user: PatchUserRequest) { + return makeRequest({ + url: apiUrl + "/user/", + contentType: true, + method: "PATCH", + useToken: true, + withCredentials: false, + body: user, + }); +} \ No newline at end of file diff --git a/src/components/ComplexNavText.tsx b/src/components/ComplexNavText.tsx index 81432bb..82c04da 100644 --- a/src/components/ComplexNavText.tsx +++ b/src/components/ComplexNavText.tsx @@ -2,43 +2,45 @@ import { Typography, useTheme } from "@mui/material"; import { useNavigate } from "react-router-dom"; interface Props { - text1: string; - text2: string; + text1: string; + text2?: string; } export default function ComplexNavText({ text1, text2 }: Props) { - const theme = useTheme(); - const navigate = useNavigate(); + const theme = useTheme(); + const navigate = useNavigate(); - return ( - - navigate("/tariffs")} - sx={{ - cursor: "pointer", - fontWeight: 400, - fontSize: "12px", - lineHeight: "14px", - color: theme.palette.grey2.main, - }} - > - {text1} - - - {text2} - - - ); + return ( + + navigate("/tariffs")} + sx={{ + cursor: "pointer", + fontWeight: 400, + fontSize: "12px", + lineHeight: "14px", + color: theme.palette.grey2.main, + }} + > + {text1} + + {text2 && + + {text2} + + } + + ); } diff --git a/src/components/UnderlinedButtonWithIcon.tsx b/src/components/UnderlinedButtonWithIcon.tsx new file mode 100644 index 0000000..591fbaf --- /dev/null +++ b/src/components/UnderlinedButtonWithIcon.tsx @@ -0,0 +1,45 @@ +import { Button, ButtonProps, SxProps, Theme, useMediaQuery, useTheme } from "@mui/material"; +import { MouseEventHandler, ReactNode } from "react"; + + +interface Props { + icon?: ReactNode; + ButtonProps?: ButtonProps; + children?: ReactNode; + sx?: SxProps; + onClick?: MouseEventHandler; +} + +export default function UnderlinedButtonWithIcon({ ButtonProps, icon, children, sx, onClick }: Props) { + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/icons/CloseSmallIcon.tsx b/src/components/icons/CloseSmallIcon.tsx new file mode 100644 index 0000000..a043fba --- /dev/null +++ b/src/components/icons/CloseSmallIcon.tsx @@ -0,0 +1,21 @@ +import { Box } from "@mui/material"; + + +export default function CloseSmallIcon() { + + return ( + + + + + + + ); +} \ No newline at end of file diff --git a/src/components/icons/EyeIcon.tsx b/src/components/icons/EyeIcon.tsx new file mode 100644 index 0000000..6785638 --- /dev/null +++ b/src/components/icons/EyeIcon.tsx @@ -0,0 +1,21 @@ +import { Box } from "@mui/material"; + + +export default function EyeIcon() { + + return ( + + + + + + + ); +} \ No newline at end of file diff --git a/src/components/icons/PaperClipIcon.tsx b/src/components/icons/PaperClipIcon.tsx new file mode 100644 index 0000000..178151d --- /dev/null +++ b/src/components/icons/PaperClipIcon.tsx @@ -0,0 +1,24 @@ +import { Box } from "@mui/material"; + + +interface Props { + color?: string; +} + +export default function PaperClipIcon({ color = "#7E2AEA" }: Props) { + + return ( + + + + + + ); +} \ No newline at end of file diff --git a/src/components/icons/UploadIcon.tsx b/src/components/icons/UploadIcon.tsx new file mode 100644 index 0000000..6c0add8 --- /dev/null +++ b/src/components/icons/UploadIcon.tsx @@ -0,0 +1,22 @@ +import { Box } from "@mui/material"; + + +export default function UploadIcon() { + + return ( + + + + + + + + ); +} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index eada882..93faccf 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import Faq from "./pages/Faq/Faq"; import Wallet from "./pages/Wallet"; import Payment from "./pages/Payment/Payment"; import Support from "./pages/Support/Support"; -import AccountSetup from "./pages/AccountSetup"; +import AccountSetup from "./pages/AccountSetup/AccountSetup"; import Landing from "./pages/Landing/Landing"; import Tariffs from "./pages/Tariffs/Tariffs"; import SigninDialog from "./pages/auth/Signin"; @@ -20,7 +20,7 @@ import reportWebVitals from "./reportWebVitals"; import { SnackbarProvider, enqueueSnackbar } from "notistack"; import "./index.css"; import Layout from "./components/Layout"; -import { getUser } from "./api/auth"; +import { getUser } from "./api/user"; import { setUser, useUserStore } from "./stores/user"; import TariffConstructor from "./pages/TariffConstructor/TariffConstructor"; diff --git a/src/model/auth.ts b/src/model/auth.ts index f0e6c76..f4ebcf2 100644 --- a/src/model/auth.ts +++ b/src/model/auth.ts @@ -1,5 +1,3 @@ - - export interface RegisterRequest { login: string; password: string; @@ -21,14 +19,3 @@ export interface LoginRequest { } export type LoginResponse = RegisterResponse; - -export interface User { - _id: string; - login: string; - email: string; - phoneNumber: string; - isDeleted: boolean; - createdAt: string; - updatedAt: string; - deletedAt?: string; -} \ No newline at end of file diff --git a/src/model/user.ts b/src/model/user.ts new file mode 100644 index 0000000..77e35ce --- /dev/null +++ b/src/model/user.ts @@ -0,0 +1,44 @@ +export interface User { + _id: string; + login: string; + email: string; + phoneNumber: string; + isDeleted: boolean; + createdAt: string; + updatedAt: string; + deletedAt?: string; +} + +export type UserWithFields = User & Partial>; + +export type UserSettingsField = + | "password" + | "email" + | "phoneNumber" + | "name" + | "surname" + | "middleName" + | "companyName"; + +export type UserSettings = Record; + +export type PatchUserRequest = Partial>; + +export type VerificationStatus = "verificated" | "notVerificated" | "waiting"; + +export type UserDocument = { + file: File | null; + uploadedFileName: string | null; + imageSrc: string | null; +}; + +export type UserDocumentTypes = + | "ИНН" + | "Устав" + | "Свидетельство о регистрации НКО"; + +export type UserDocuments = Record; diff --git a/src/pages/AccountSetup.tsx b/src/pages/AccountSetup.tsx deleted file mode 100644 index a4a423e..0000000 --- a/src/pages/AccountSetup.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import { Box, Button, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; -import CustomButton from "@components/CustomButton"; -import InputTextfield from "@components/InputTextfield"; -import SectionWrapper from "@components/SectionWrapper"; - -import Download from "../assets/Icons/Download.svg"; -import Account from "../assets/Icons/Account.svg"; -import { useState } from "react"; - -export default function AccountSetup() { - const theme = useTheme(); - const upMd = useMediaQuery(theme.breakpoints.up("md")); - - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [surname, setSurname] = useState(""); - const [telephone, setTelephone] = useState(""); - const [otchestvo, setOtchestvo] = useState(""); - const [password, setPassword] = useState(""); - const [сompanyName, setCompanyName] = useState(""); - - const [avatar, setAvatar] = useState(); - - const imgHC = (imgInp: any) => { - if (imgInp.target.files !== null) { - const file = imgInp.target.files[0]; - setAvatar(URL.createObjectURL(file)); - handleClose(); - } - }; - - return ( - - - Настройки аккаунта - - - - Настройки аккаунта - - - - - - - Account - - - - - - - setName(event.target.value)} - id="text" - label="Имя" - gap={upMd ? "15px" : "10px"} - color={name ? "#e8badd" : ""} - FormInputSx={{ width: upMd ? "45%" : "100%", mt: "19px", maxWidth: "406px" }} - /> - - setEmail(event.target.value)} - id="email" - label="E-mail" - gap={upMd ? "15px" : "10px"} - FormInputSx={{ width: upMd ? "45%" : "100%", mt: "19px", maxWidth: "406px" }} - color={email ? "#e8badd" : ""} - /> - - setSurname(event.target.value)} - id="password" - label="Фамилия" - gap={upMd ? "15px" : "10px"} - FormInputSx={{ width: upMd ? "45%" : "100%", mt: "19px", maxWidth: "406px" }} - color={surname ? "#e8badd" : ""} - /> - - setTelephone(enent.target.value)} - id="password" - label="Телефон" - gap={upMd ? "15px" : "10px"} - FormInputSx={{ width: upMd ? "45%" : "100%", mt: "19px", maxWidth: "406px" }} - color={telephone ? "#e8badd" : ""} - /> - - setOtchestvo(enent.target.value)} - id="password" - label="Отчество" - gap={upMd ? "15px" : "10px"} - FormInputSx={{ width: upMd ? "45%" : "100%", mt: "19px", maxWidth: "406px" }} - color={otchestvo ? "#e8badd" : ""} - /> - - setPassword(enent.target.value)} - id="email" - label="Пароль" - gap={upMd ? "15px" : "10px"} - FormInputSx={{ width: upMd ? "45%" : "100%", mt: "19px", maxWidth: "406px" }} - color={password ? "#e8badd" : ""} - /> - - setCompanyName(enent.target.value)} - id="text" - label="Название компании" - gap={upMd ? "15px" : "10px"} - FormInputSx={{ width: upMd ? "45%" : "100%", mt: "19px", maxWidth: "406px" }} - color={сompanyName ? " #e8badd" : ""} - /> - - - - Download - - Загрузить документы для юр лиц - - - - - Download - - Загрузить документы для НКО - - - - - - Cохранить - - - - - - - ); -} -function handleClose() { - throw new Error("Function not implemented."); -} diff --git a/src/pages/AccountSetup/AccountSetup.tsx b/src/pages/AccountSetup/AccountSetup.tsx new file mode 100644 index 0000000..1f9e571 --- /dev/null +++ b/src/pages/AccountSetup/AccountSetup.tsx @@ -0,0 +1,246 @@ +import { Box, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material"; +import CustomButton from "@components/CustomButton"; +import InputTextfield from "@components/InputTextfield"; +import SectionWrapper from "@components/SectionWrapper"; +import ComplexNavText from "@root/components/ComplexNavText"; +import { openDocumentsDialog, sendUserData, setSettingsField, useUserStore } from "@root/stores/user"; +import UnderlinedButtonWithIcon from "@root/components/UnderlinedButtonWithIcon"; +import UploadIcon from "@root/components/icons/UploadIcon"; +import { VerificationStatus } from "@root/model/user"; +import DocumentsDialog from "./DocumentsDialog/DocumentsDialog"; +import EyeIcon from "@root/components/icons/EyeIcon"; + + +export default function AccountSetup() { + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const upSm = useMediaQuery(theme.breakpoints.up("sm")); + const fields = useUserStore(state => state.settingsFields); + const verificationStatus = useUserStore(state => state.verificationStatus); + const verificationType = useUserStore(state => state.verificationType); + + const textFieldProps = { + gap: upMd ? "16px" : "10px", + color: "#F2F3F7", + bold: true, + }; + + return ( + + + + Настройки аккаунта + + + + setSettingsField("name", e.target.value)} + id="name" + label="Имя" + {...textFieldProps} + /> + setSettingsField("surname", e.target.value)} + id="surname" + label="Фамилия" + {...textFieldProps} + /> + setSettingsField("middleName", e.target.value)} + id="middleName" + label="Отчество" + {...textFieldProps} + /> + setSettingsField("companyName", e.target.value)} + id="companyName" + label="Название компании" + {...textFieldProps} + /> + setSettingsField("email", e.target.value)} + id="email" + label="E-mail" + {...textFieldProps} + /> + setSettingsField("phoneNumber", e.target.value)} + id="phoneNumber" + label="Телефон" + {...textFieldProps} + /> + setSettingsField("password", e.target.value)} + id="password" + label="Пароль" + {...textFieldProps} + /> + + + Статус + + {verificationStatus === "notVerificated" && + <> + } + sx={{ mt: "55px" }} + ButtonProps={{ + onClick: () => openDocumentsDialog("juridical"), + }} + >Загрузить документы для юр лиц + } + sx={{ mt: "15px" }} + ButtonProps={{ + onClick: () => openDocumentsDialog("nko"), + }} + >Загрузить документы для НКО + + } + {verificationStatus === "verificated" && + } + sx={{ mt: "55px" }} + ButtonProps={{ + onClick: () => openDocumentsDialog(verificationType), + }} + >Посмотреть свою верификацию + } + + + + Сохранить + + + + ); +} + +const verificationStatusData: Record = { + "verificated": { + text: "Верификация пройдена", + color: "#0D9F00", + }, + "waiting": { + text: "В ожидании верификации", + color: "#F18956", + }, + "notVerificated": { + text: "Не верифицирован", + color: "#E02C2C", + }, +}; + +function VerificationIndicator({ verificationStatus, sx }: { + verificationStatus: VerificationStatus; + sx?: SxProps; +}) { + return ( + + {verificationStatusData[verificationStatus].text} + + ); +} \ No newline at end of file diff --git a/src/pages/AccountSetup/DocumentsDialog/DocumentItem.tsx b/src/pages/AccountSetup/DocumentsDialog/DocumentItem.tsx new file mode 100644 index 0000000..080b065 --- /dev/null +++ b/src/pages/AccountSetup/DocumentsDialog/DocumentItem.tsx @@ -0,0 +1,45 @@ +import { Box, SxProps, Theme, Typography, useTheme } from "@mui/material"; +import { UserDocument } from "@root/model/user"; + + +interface Props { + text: string; + document: UserDocument; + sx?: SxProps; +} + +export default function DocumentItem({ text, document, sx }: Props) { + const theme = useTheme(); + + return ( + + {text} + {document.uploadedFileName && + {document.uploadedFileName} + } + {document.imageSrc && + document} + + ); +} \ No newline at end of file diff --git a/src/pages/AccountSetup/DocumentsDialog/DocumentUploadItem.tsx b/src/pages/AccountSetup/DocumentsDialog/DocumentUploadItem.tsx new file mode 100644 index 0000000..328a0c7 --- /dev/null +++ b/src/pages/AccountSetup/DocumentsDialog/DocumentUploadItem.tsx @@ -0,0 +1,62 @@ +import { Box, SxProps, Theme, Typography } from "@mui/material"; +import UnderlinedButtonWithIcon from "@root/components/UnderlinedButtonWithIcon"; +import PaperClipIcon from "@root/components/icons/PaperClipIcon"; +import { UserDocument } from "@root/model/user"; +import { ChangeEvent, useRef } from "react"; + + +interface Props { + text: string; + document: UserDocument; + onFileChange: (event: ChangeEvent) => void; + sx?: SxProps; +} + +export default function DocumentUploadItem({ text, document, onFileChange, sx }: Props) { + const fileInputRef = useRef(null); + + function handleChooseFileClick() { + fileInputRef.current?.click(); + } + + return ( + + {text} + } + onClick={handleChooseFileClick} + >{document.file ? document.file.name : "Выберите файл"} + + {document.imageSrc && + document + } + + ); +} \ No newline at end of file diff --git a/src/pages/AccountSetup/DocumentsDialog/DocumentsDialog.tsx b/src/pages/AccountSetup/DocumentsDialog/DocumentsDialog.tsx new file mode 100644 index 0000000..8ad7209 --- /dev/null +++ b/src/pages/AccountSetup/DocumentsDialog/DocumentsDialog.tsx @@ -0,0 +1,10 @@ +import { useUserStore } from "@root/stores/user"; +import NkoDocumentsDialog from "./NkoDocumentsDialog"; +import JuridicalDocumentsDialog from "./JuridicalDocumentsDialog"; + + +export default function DocumentsDialog() { + const type = useUserStore(state => state.dialogType); + + return type === "juridical" ? : +} \ No newline at end of file diff --git a/src/pages/AccountSetup/DocumentsDialog/JuridicalDocumentsDialog.tsx b/src/pages/AccountSetup/DocumentsDialog/JuridicalDocumentsDialog.tsx new file mode 100644 index 0000000..dea39e1 --- /dev/null +++ b/src/pages/AccountSetup/DocumentsDialog/JuridicalDocumentsDialog.tsx @@ -0,0 +1,107 @@ +import { Box, Dialog, IconButton, Typography, useTheme } from "@mui/material"; +import CustomButton from "@root/components/CustomButton"; +import CloseSmallIcon from "@root/components/icons/CloseSmallIcon"; +import { closeDocumentsDialog, sendDocuments, setDocument, useUserStore } from "@root/stores/user"; +import DocumentUploadItem from "./DocumentUploadItem"; +import DocumentItem from "./DocumentItem"; + + +export default function JuridicalDocumentsDialog() { + const theme = useTheme(); + const isOpen = useUserStore(state => state.isDocumentsDialogOpen); + const verificationStatus = useUserStore(state => state.verificationStatus); + const documents = useUserStore(state => state.documents); + + const documentElements = verificationStatus === "verificated" ? ( + <> + + + + ) : ( + <> + setDocument("ИНН", e.target?.files?.[0])} + /> + setDocument("Устав", e.target?.files?.[0])} + /> + + ); + + return ( + + + + + + + {verificationStatus === "verificated" ? "Ваши документы" : "Загрузите документы"} + + для верификации юридических лиц в формате PDF + + {documentElements} + + + + Отправить + + + ); +} \ No newline at end of file diff --git a/src/pages/AccountSetup/DocumentsDialog/NkoDocumentsDialog.tsx b/src/pages/AccountSetup/DocumentsDialog/NkoDocumentsDialog.tsx new file mode 100644 index 0000000..177682c --- /dev/null +++ b/src/pages/AccountSetup/DocumentsDialog/NkoDocumentsDialog.tsx @@ -0,0 +1,116 @@ +import { Box, Dialog, IconButton, Typography, useTheme } from "@mui/material"; +import CustomButton from "@root/components/CustomButton"; +import CloseSmallIcon from "@root/components/icons/CloseSmallIcon"; +import { closeDocumentsDialog, sendDocuments, setDocument, useUserStore } from "@root/stores/user"; +import DocumentUploadItem from "./DocumentUploadItem"; +import DocumentItem from "./DocumentItem"; + + +export default function NkoDocumentsDialog() { + const theme = useTheme(); + const isOpen = useUserStore(state => state.isDocumentsDialogOpen); + const verificationStatus = useUserStore(state => state.verificationStatus); + const documents = useUserStore(state => state.documents); + + const documentElements = verificationStatus === "verificated" ? ( + <> + + + + + ) : ( + <> + setDocument("Свидетельство о регистрации НКО", e.target?.files?.[0])} + /> + setDocument("ИНН", e.target?.files?.[0])} + /> + setDocument("Устав", e.target?.files?.[0])} + /> + + ); + + return ( + + + + + + + {verificationStatus === "verificated" ? "Ваши документы" : "Загрузите документы"} + + для верификации НКО в формате PDF + + {documentElements} + + + + Отправить + + + ); +} \ No newline at end of file diff --git a/src/pages/auth/Signin.tsx b/src/pages/auth/Signin.tsx index e53c11a..cedba04 100644 --- a/src/pages/auth/Signin.tsx +++ b/src/pages/auth/Signin.tsx @@ -44,8 +44,8 @@ export default function SigninDialog() { makeRequest({ url: "https://hub.pena.digital/auth/login", body: { - login: values.login, - password: values.password, + login: values.login.trim(), + password: values.password.trim(), }, useToken: false, withCredentials: true, @@ -76,38 +76,39 @@ export default function SigninDialog() { setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen); } - return ( - - + ({ url: "https://hub.pena.digital/auth/register", body: { - login: values.login, - password: values.password, + login: values.login.trim(), + password: values.password.trim(), phoneNumber: "-", }, useToken: false, @@ -82,6 +82,7 @@ export default function SignupDialog() { PaperProps={{ sx: { width: "600px", + maxWidth: "600px", } }} slotProps={{ diff --git a/src/stores/makeRequest.ts b/src/stores/makeRequest.ts index 8b316f8..52288a9 100644 --- a/src/stores/makeRequest.ts +++ b/src/stores/makeRequest.ts @@ -14,9 +14,11 @@ interface FirstRequest { method?: string; url: string; body?: T; + /** Send access token */ useToken?: boolean; contentType?: boolean; signal?: AbortSignal; + /** Send refresh token */ withCredentials?: boolean; } diff --git a/src/stores/user.ts b/src/stores/user.ts index e7c74c8..fa437b5 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -1,16 +1,57 @@ -import { User } from "@root/model/auth"; +import { PatchUserRequest, UserDocumentTypes, UserDocuments, UserSettings, UserSettingsField, UserWithFields, VerificationStatus } from "@root/model/user"; +import { produce } from "immer"; import { create } from "zustand"; import { createJSONStorage, devtools, persist } from "zustand/middleware"; +import { StringSchema, string } from "yup"; +import { patchUser } from "@root/api/user"; interface UserStore { userId: string | null; - user: User | null; + user: UserWithFields | null; + settingsFields: UserSettings | null; + verificationStatus: VerificationStatus; + verificationType: "juridical" | "nko"; + isDocumentsDialogOpen: boolean; + dialogType: "juridical" | "nko"; + documents: UserDocuments; } +const defaultFieldValues = { + value: "", + error: null, + touched: false, +}; + +const defaultFields = { + name: { ...defaultFieldValues }, + surname: { ...defaultFieldValues }, + middleName: { ...defaultFieldValues }, + companyName: { ...defaultFieldValues }, + email: { ...defaultFieldValues }, + phoneNumber: { ...defaultFieldValues }, + password: { ...defaultFieldValues }, +}; + +const defaultDocument = { + file: null, + uploadedFileName: null, + imageSrc: null, +}; + const initialState: UserStore = { userId: null, user: null, + settingsFields: { ...defaultFields }, + verificationStatus: "notVerificated", + verificationType: "juridical", + isDocumentsDialogOpen: false, + dialogType: "juridical", + documents: { + "ИНН": { ...defaultDocument }, + "Устав": { ...defaultDocument }, + "Свидетельство о регистрации НКО": { ...defaultDocument }, + } }; export const useUserStore = create()( @@ -19,16 +60,159 @@ export const useUserStore = create()( (set, get) => initialState, { name: "User store", + enabled: process.env.NODE_ENV === "development", } ), { + version: 1, name: "user", storage: createJSONStorage(() => localStorage), + partialize: state => ({ + userId: state.userId, + user: state.user, + }) } ) ); export const setUserId = (userId: string | null) => useUserStore.setState({ userId }); -export const setUser = (user: User | null) => useUserStore.setState({ user }); -export const clearUser = () => useUserStore.setState({ ...initialState }); \ No newline at end of file +export const openDocumentsDialog = (type: UserStore["dialogType"]) => useUserStore.setState( + produce(state => { + state.isDocumentsDialogOpen = true; + state.dialogType = type; + }) +); + +export const closeDocumentsDialog = () => useUserStore.setState( + produce(state => { + state.isDocumentsDialogOpen = false; + }) +); + +export const setDocument = (type: UserDocumentTypes, file: File | undefined) => useUserStore.setState( + produce(state => { + if (!file) { + state.documents[type] = { ...defaultDocument }; + return; + } + + let imageSrc: string | null = null; + + try { + const src = state.documents[type].imageSrc; + if (src) URL.revokeObjectURL(src); + + imageSrc = URL.createObjectURL(file); + } catch (error) { + console.log(error); + } + + state.documents[type] = { + file, + uploadedFileName: null, + imageSrc, + }; + }) +); + +export const setUploadedDocument = (type: UserDocumentTypes, fileName: string, url: string) => useUserStore.setState( + produce(state => { + state.documents[type] = { + file: null, + uploadedFileName: fileName, + imageSrc: url, + }; + }) +); + +export const sendDocuments = () => { + const state = useUserStore.getState(); + const type = state.dialogType; + const documents = state.documents; + + + + // const formData = new FormData(); + // formData.append("file1", file1); + // formData.append("file2", file2); + + // revoke on success + // Object.values(documents).map(document => document?.imageSrc).forEach(src => { + // if (src) URL.revokeObjectURL(src); + // }); + + // useUserStore.setState(produce(state => { + // state.isDocumentsDialogOpen = false; + // })); +}; + +export const setUser = (user: UserWithFields | null) => useUserStore.setState( + produce(state => { + state.user = user; + if (!user) { + state.settingsFields = null; + return; + } + + if (!state.settingsFields) state.settingsFields = { ...defaultFields }; + + state.settingsFields.name.value = user.name || ""; + state.settingsFields.surname.value = user.surname || ""; + state.settingsFields.middleName.value = user.middleName || ""; + state.settingsFields.companyName.value = user.companyName || ""; + state.settingsFields.email.value = user.email; + state.settingsFields.phoneNumber.value = user.phoneNumber; + state.settingsFields.password.value = ""; + }) +); + +export const clearUser = () => useUserStore.setState({ ...initialState }); + +export const setSettingsField = ( + fieldName: UserSettingsField, + value: string, +) => useUserStore.setState( + produce(state => { + if (!state.settingsFields) return; + + let errorMessage: string | null = null; + + try { + validators[fieldName].validateSync(value); + } catch (error: any) { + errorMessage = error.message; + } + + state.settingsFields[fieldName].value = value || ""; + state.settingsFields[fieldName].touched = true; + state.settingsFields[fieldName].error = errorMessage; + }) +); + + +export const sendUserData = async () => { + const state = useUserStore.getState(); + if (!state.settingsFields) return; + + const payload: PatchUserRequest = {}; + + for (const [fieldName, fieldValue] of Object.entries(state.settingsFields)) { + if ( + fieldValue.value !== (state.user?.[fieldName as UserSettingsField] ?? "") + ) payload[fieldName as UserSettingsField] = fieldValue.value; + } + + const user = await patchUser(payload); + setUser(user); +}; + +const validators: Record = { + name: string(), + email: string().email("Неверный email"), + surname: string(), + phoneNumber: string().matches(/^[+\d|\d]*$/, "Неверный номер телефона").min(6, "Номер телефона должен содержать минимум 6 символов"), + middleName: string(), + password: string().min(8, "Минимум 8 символов").matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы в пароле"), + companyName: string(), +}; \ No newline at end of file