376 lines
11 KiB
TypeScript
376 lines
11 KiB
TypeScript
import {
|
||
PatchUserRequest,
|
||
UserDocumentTypes,
|
||
UserDocuments,
|
||
UserDocumentsUrl,
|
||
UserSettingsFieldStatus,
|
||
UserSettingsField,
|
||
} 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"
|
||
import { UserAccountSettingsFieldStatus, VerificationStatus } from "@root/model/account"
|
||
import { patchCurrency, deleteCart, patchCart } from "@root/api/cart"
|
||
import { User, UserAccount, UserName, getInitials, patchUserAccount } from "@frontend/kitui"
|
||
import { cancelPayCartProcess, setNotEnoughMoneyAmount, setSiteReadyPayCart, useNotEnoughMoneyAmount } from "./notEnoughMoneyAmount";
|
||
|
||
type Privilege = {
|
||
amount: number;
|
||
created_at: string;
|
||
id: string;
|
||
privilege_id: string;
|
||
privilege_name: string;
|
||
};
|
||
|
||
interface UserStore {
|
||
userId: string | null;
|
||
user: User | null;
|
||
userAccount: UserAccount | null;
|
||
settingsFields: UserSettingsFieldStatus & UserAccountSettingsFieldStatus & { hasError: boolean };
|
||
verificationStatus: VerificationStatus;
|
||
verificationType: "juridical" | "nko";
|
||
isDocumentsDialogOpen: boolean;
|
||
dialogType: "juridical" | "nko" | "";
|
||
documents: UserDocuments;
|
||
documentsUrl: UserDocumentsUrl;
|
||
comment: string;
|
||
initials: string;
|
||
quizUserAccount: OriginalUserSquizAccount | null;
|
||
}
|
||
|
||
|
||
export type OriginalUserSquizAccount = {
|
||
created_at: string;
|
||
deleted: boolean;
|
||
email: string;
|
||
id: string;
|
||
privileges: Record<string, Privilege>;
|
||
privilege_name: string;
|
||
};
|
||
|
||
const defaultFieldValues = {
|
||
value: "",
|
||
error: null,
|
||
touched: false,
|
||
}
|
||
|
||
const defaultFields: UserStore["settingsFields"] = {
|
||
firstname: { ...defaultFieldValues },
|
||
secondname: { ...defaultFieldValues },
|
||
middlename: { ...defaultFieldValues },
|
||
orgname: { ...defaultFieldValues },
|
||
email: { ...defaultFieldValues },
|
||
phoneNumber: { ...defaultFieldValues },
|
||
password: { ...defaultFieldValues },
|
||
hasError: false,
|
||
}
|
||
|
||
export const defaultDocument = {
|
||
file: null,
|
||
uploadedFileName: null,
|
||
imageSrc: null,
|
||
}
|
||
|
||
const initialState: UserStore = {
|
||
userId: null,
|
||
user: null,
|
||
userAccount: null,
|
||
quizUserAccount: null,
|
||
settingsFields: { ...defaultFields },
|
||
verificationStatus: VerificationStatus.NOT_VERIFICATED,
|
||
verificationType: "juridical",
|
||
isDocumentsDialogOpen: false,
|
||
dialogType: "",
|
||
comment: "",
|
||
documents: {
|
||
ИНН: { ...defaultDocument },
|
||
Устав: { ...defaultDocument },
|
||
"Свидетельство о регистрации НКО": { ...defaultDocument },
|
||
},
|
||
documentsUrl: {
|
||
ИНН: "",
|
||
Устав: "",
|
||
"Свидетельство о регистрации НКО": "",
|
||
},
|
||
initials: "AA",
|
||
}
|
||
|
||
export const useUserStore = create<UserStore>()(
|
||
persist(
|
||
devtools((set, get) => initialState, {
|
||
name: "User",
|
||
enabled: process.env.NODE_ENV === "development",
|
||
trace: true,
|
||
}),
|
||
{
|
||
version: 2,
|
||
name: "user",
|
||
storage: createJSONStorage(() => localStorage),
|
||
partialize: (state) => ({ // список полей для хранения в ЛС
|
||
userId: state.userId,
|
||
user: state.user,
|
||
}),
|
||
migrate: (persistedState, version) => ({
|
||
...(persistedState as UserStore),
|
||
user: null,
|
||
}),
|
||
}
|
||
)
|
||
)
|
||
|
||
export const setVerificationStatus = (verificationStatus: VerificationStatus) =>
|
||
useUserStore.setState({ verificationStatus })
|
||
|
||
export const setVerificationType = (verificationType: UserAccount["status"]) =>
|
||
useUserStore.setState({
|
||
verificationType: verificationType === "org" ? "juridical" : "nko",
|
||
})
|
||
export const setUserStatus = (status: UserAccount["status"]) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
if (state.userAccount !== null) state.userAccount.status = status
|
||
})
|
||
)
|
||
export const setWallet = (wallet: UserAccount["wallet"]) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
if (state.userAccount !== null) state.userAccount.wallet = wallet
|
||
})
|
||
)
|
||
|
||
export const setUserId = (userId: string | null) => useUserStore.setState({ userId })
|
||
export const setUser = (user: User) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
state.user = user
|
||
|
||
state.settingsFields.email.value = user?.email || user?.login || ""
|
||
state.settingsFields.phoneNumber.value = user?.phoneNumber ?? ""
|
||
state.settingsFields.password.value = ""
|
||
})
|
||
)
|
||
|
||
export const setUserAccount = (user: UserAccount) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
state.userAccount = user
|
||
|
||
state.settingsFields.firstname.value = user?.name.firstname ?? ""
|
||
state.settingsFields.secondname.value = user?.name.secondname ?? ""
|
||
state.settingsFields.middlename.value = user?.name.middlename ?? ""
|
||
state.settingsFields.orgname.value = user?.name.orgname ?? ""
|
||
|
||
state.initials = getInitials(state.settingsFields.firstname.value, state.settingsFields.secondname.value)
|
||
}),
|
||
false,
|
||
{
|
||
type: "setUserAccount",
|
||
payload: user,
|
||
}
|
||
)
|
||
|
||
export const setQuizUserAccount = (quizUserAccount: OriginalUserSquizAccount) => useUserStore.setState({ quizUserAccount });
|
||
|
||
export const setNewNames = (name: UserName) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
state.settingsFields.firstname.value = name.firstname ?? ""
|
||
state.settingsFields.secondname.value = name.secondname ?? ""
|
||
state.settingsFields.middlename.value = name.middlename ?? ""
|
||
state.settingsFields.orgname.value = name.orgname ?? ""
|
||
}),
|
||
false,
|
||
{
|
||
type: "setNewNames",
|
||
payload: name,
|
||
}
|
||
)
|
||
|
||
export const setCart = (cart: string[]) => {
|
||
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
if (state.userAccount) {
|
||
state.userAccount.cart = cart
|
||
}
|
||
})
|
||
)
|
||
//Изменение корзины ведёт к отмене этих 2 параметров всегда
|
||
setNotEnoughMoneyAmount(0)
|
||
cancelPayCartProcess()
|
||
}
|
||
|
||
export const setComment = (comment: string) => useUserStore.setState({ comment })
|
||
|
||
export const clearUserData = () => useUserStore.setState({ ...initialState })
|
||
|
||
export const openDocumentsDialog = (type: UserStore["dialogType"]) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
state.isDocumentsDialogOpen = true
|
||
state.dialogType = type
|
||
})
|
||
)
|
||
|
||
export const closeDocumentsDialog = () =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
state.isDocumentsDialogOpen = false
|
||
state.dialogType = ""
|
||
})
|
||
)
|
||
|
||
export const setDocument = (type: UserDocumentTypes, file: File | null) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((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.error("Error creating object url", error)
|
||
}
|
||
|
||
state.documents[type] = {
|
||
file,
|
||
uploadedFileName: null,
|
||
imageSrc,
|
||
}
|
||
})
|
||
)
|
||
|
||
export const setDocumentUrl = (type: UserDocumentTypes, url: string) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
if (!url) {
|
||
state.documentsUrl[type] = ""
|
||
return
|
||
}
|
||
|
||
state.documentsUrl[type] = url
|
||
})
|
||
)
|
||
|
||
export const setUploadedDocument = (type: UserDocumentTypes, fileName: string, url: string) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
state.documents[type] = {
|
||
file: null,
|
||
uploadedFileName: fileName,
|
||
imageSrc: url,
|
||
}
|
||
})
|
||
)
|
||
|
||
export const setSettingsField = (fieldName: UserSettingsField | keyof UserName, value: string) =>
|
||
useUserStore.setState(
|
||
produce<UserStore>((state) => {
|
||
if (!state.settingsFields) return
|
||
|
||
let errorMessage: string | null = null
|
||
|
||
try {
|
||
if (value) 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
|
||
|
||
state.settingsFields.hasError = Object.values(state.settingsFields).reduce((acc: boolean, field) => {
|
||
if (typeof field === "boolean") return acc
|
||
|
||
if (field.error !== null) return true
|
||
return acc
|
||
}, false)
|
||
})
|
||
)
|
||
|
||
export const sendUserData = async () => {
|
||
const state = useUserStore.getState()
|
||
if (!state.settingsFields) return
|
||
|
||
const isPatchingUser =
|
||
state.settingsFields.email.touched ||
|
||
state.settingsFields.password.touched ||
|
||
state.settingsFields.phoneNumber.touched
|
||
|
||
const isPatchingUserAccount =
|
||
state.settingsFields.firstname.touched ||
|
||
state.settingsFields.secondname.touched ||
|
||
state.settingsFields.middlename.touched ||
|
||
state.settingsFields.orgname.touched
|
||
|
||
const userPayload: PatchUserRequest = {}
|
||
|
||
if (state.settingsFields.email.value.length !== 0) userPayload.email = state.settingsFields.email.value
|
||
if (state.settingsFields.password.value.length !== 0) userPayload.password = state.settingsFields.password.value
|
||
if (state.settingsFields.phoneNumber.value.length !== 0)
|
||
userPayload.phoneNumber = state.settingsFields.phoneNumber.value
|
||
|
||
const userAccountPayload: UserName = {
|
||
firstname: state.settingsFields.firstname.value,
|
||
secondname: state.settingsFields.secondname.value,
|
||
middlename: state.settingsFields.middlename.value,
|
||
orgname: state.settingsFields.orgname.value,
|
||
}
|
||
|
||
await Promise.all([
|
||
isPatchingUser && patchUser(userPayload).then(([user]) => user && setUser(user)),
|
||
isPatchingUserAccount && patchUserAccount(userAccountPayload, "v1.0.1").then(setUserAccount),
|
||
])
|
||
}
|
||
|
||
export const addTariffToCart = async (tariffId: string) => {
|
||
const [patchCartResponse, patchCartError] = await patchCart(tariffId)
|
||
|
||
if (patchCartError === undefined) {
|
||
setCart(patchCartResponse)
|
||
}
|
||
return ({ patchCartResponse, patchCartError })
|
||
}
|
||
|
||
export const removeTariffFromCart = async (tariffId: string) => {
|
||
setNotEnoughMoneyAmount(0);
|
||
const [deleteCartResponse, deleteCartError] = await deleteCart(tariffId)
|
||
|
||
if (!deleteCartError) {
|
||
setCart(deleteCartResponse)
|
||
}
|
||
}
|
||
|
||
export const changeUserCurrency = async (currency: string) => {
|
||
const [patchCurrencyResponse, patchCurrencyError] = await patchCurrency(currency)
|
||
|
||
if (!patchCurrencyError && patchCurrencyResponse) {
|
||
setUserAccount(patchCurrencyResponse)
|
||
}
|
||
}
|
||
|
||
const validators: Record<UserSettingsField | keyof UserName, StringSchema> = {
|
||
email: string().email("Неверный email"),
|
||
phoneNumber: string()
|
||
.matches(/^[+\d|\d]*$/, "Неверный номер телефона")
|
||
.min(6, "Номер телефона должен содержать минимум 6 символов"),
|
||
password: string()
|
||
.min(8, "Минимум 8 символов")
|
||
.matches(/^[.,:;\-_+!&()*<>\[\]\{\}`@"#$\%\^\=?\d\w]+$/, "Некорректные символы в пароле")
|
||
.optional(),
|
||
firstname: string(),
|
||
secondname: string(),
|
||
middlename: string(),
|
||
orgname: string(),
|
||
}
|