Merge branch 'dev' into 'staging'

предупреждение о смене логина пароля и обработка ошибки корзины

See merge request frontend/marketplace!129
This commit is contained in:
Nastya 2024-02-19 20:48:34 +00:00
commit 9d4f22812b
8 changed files with 195 additions and 43 deletions

@ -47,12 +47,13 @@ function Drawers() {
if (payCartError) { if (payCartError) {
if (payCartError.includes("insufficient funds: ")) { if (payCartError.includes("insufficient funds: ")) {
const notEnoughMoneyAmount = parseInt(payCartError.replace(/^.*insufficient\sfunds:\s(?=\d+$)/, "")); const notEnoughMoneyAmount = parseInt(payCartError.replace(/^.*insufficient\sfunds:\s(?=\d+$)/, ""));
setNotEnoughMoneyAmount(notEnoughMoneyAmount); setNotEnoughMoneyAmount(notEnoughMoneyAmount);
} }
setLoading(false); setLoading(false);
closeCartDrawer()
navigate("payment")
return enqueueSnackbar(payCartError); return enqueueSnackbar(payCartError);
} }
@ -321,7 +322,7 @@ console.log('aaaaaaaaaaAAAAAAAAAAAAAA', (cart.appliedCartPurchasesDiscount?.Targ
onClick={() => (notEnoughMoneyAmount === 0 ? !loading && handlePayClick() : handleReplenishWallet())} onClick={() => (notEnoughMoneyAmount === 0 ? !loading && handlePayClick() : handleReplenishWallet())}
sx={{ mt: "25px", display: "block" }} sx={{ mt: "25px", display: "block" }}
> >
{loading ? <Loader size={24} /> : "Оплатить"} {loading ? <Loader size={24} /> : notEnoughMoneyAmount === 0 ? "Оплатить" : "Пополнить"}
</Button> </Button>
</Box> </Box>
</Box> </Box>

@ -20,6 +20,8 @@ interface Props {
FormInputSx?: SxProps<Theme>; FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps; TextfieldProps: TextFieldProps;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void; onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
onBlur?: () => void
onFocus?: () => void
} }
export default function InputTextfield({ export default function InputTextfield({
@ -31,6 +33,8 @@ export default function InputTextfield({
TextfieldProps, TextfieldProps,
color, color,
FormInputSx, FormInputSx,
onBlur = ()=>{},
onFocus = ()=>{}
}: Props) { }: Props) {
const theme = useTheme() const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md")) const upMd = useMediaQuery(theme.breakpoints.up("md"))
@ -45,6 +49,7 @@ export default function InputTextfield({
return ( return (
<FormControl <FormControl
fullWidth fullWidth
variant="standard" variant="standard"
sx={{ sx={{
@ -68,6 +73,12 @@ export default function InputTextfield({
</InputLabel> </InputLabel>
)} )}
<TextField <TextField
onBlur={(e) => {
onBlur()
}}
onFocus={(e) => {
onFocus()
}}
{...TextfieldProps} {...TextfieldProps}
fullWidth fullWidth
id={id} id={id}

@ -8,6 +8,7 @@ import { Loader } from "./Loader";
import { currencyFormatter } from "@root/utils/currencyFormatter"; import { currencyFormatter } from "@root/utils/currencyFormatter";
import { payCart } from "@root/api/cart"; import { payCart } from "@root/api/cart";
import { setUserAccount } from "@root/stores/user"; import { setUserAccount } from "@root/stores/user";
import { useCart } from "@root/utils/hooks/useCart";
interface Props { interface Props {
priceBeforeDiscounts: number; priceBeforeDiscounts: number;
@ -19,6 +20,7 @@ export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts,
const theme = useTheme(); const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md")); const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(550)); const isMobile = useMediaQuery(theme.breakpoints.down(550));
const cart = useCart();
const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0); const [notEnoughMoneyAmount, setNotEnoughMoneyAmount] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const navigate = useNavigate(); const navigate = useNavigate();
@ -162,6 +164,7 @@ export default function TotalPrice({ priceAfterDiscounts, priceBeforeDiscounts,
)} )}
<Button <Button
variant="pena-contained-dark" variant="pena-contained-dark"
disabled = {cart.priceAfterDiscounts === 0}
onClick={() => (notEnoughMoneyAmount === 0 ? !loading && handlePayClick() : handleReplenishWallet())} onClick={() => (notEnoughMoneyAmount === 0 ? !loading && handlePayClick() : handleReplenishWallet())}
sx={{ mt: "10px" }} sx={{ mt: "10px" }}
> >

@ -23,6 +23,8 @@ interface Props {
FormInputSx?: SxProps<Theme>; FormInputSx?: SxProps<Theme>;
TextfieldProps: TextFieldProps; TextfieldProps: TextFieldProps;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void; onChange: (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void;
onBlur?: () => void
onFocus?: () => void
} }
export default function PasswordInput({ export default function PasswordInput({
@ -34,6 +36,8 @@ export default function PasswordInput({
TextfieldProps, TextfieldProps,
color, color,
FormInputSx, FormInputSx,
onBlur = () => {},
onFocus = () => {},
}: Props) { }: Props) {
const theme = useTheme() const theme = useTheme()
const upMd = useMediaQuery(theme.breakpoints.up("md")) const upMd = useMediaQuery(theme.breakpoints.up("md"))
@ -81,6 +85,8 @@ export default function PasswordInput({
{...TextfieldProps} {...TextfieldProps}
fullWidth fullWidth
id={id} id={id}
onBlur={onBlur}
onFocus={onFocus}
sx={{ sx={{
"& .MuiInputBase-root": { "& .MuiInputBase-root": {
height: "48px", height: "48px",

@ -1,4 +1,4 @@
import { useEffect } from "react" import { useEffect, useState } from "react"
import { Box, Button, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material" import { Box, Button, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material"
import InputTextfield from "@components/InputTextfield" import InputTextfield from "@components/InputTextfield"
import PasswordInput from "@components/passwordInput" import PasswordInput from "@components/passwordInput"
@ -16,6 +16,7 @@ import { VerificationStatus } from "@root/model/account"
import { verify } from "./helper" import { verify } from "./helper"
import { withErrorBoundary } from "react-error-boundary" import { withErrorBoundary } from "react-error-boundary"
import { handleComponentError } from "@root/utils/handleComponentError" import { handleComponentError } from "@root/utils/handleComponentError"
import { OnChangeLoginPassword } from "./onChangeLoginPassword"
function AccountSettings() { function AccountSettings() {
const theme = useTheme() const theme = useTheme()
@ -24,12 +25,39 @@ function AccountSettings() {
const isTablet = useMediaQuery(theme.breakpoints.down(1000)) const isTablet = useMediaQuery(theme.breakpoints.down(1000))
const isMobile = useMediaQuery(theme.breakpoints.down(600)) const isMobile = useMediaQuery(theme.breakpoints.down(600))
const fields = useUserStore((state) => state.settingsFields) const { settingsFields, user } = useUserStore((state) => state)
const verificationStatus = useUserStore((state) => state.verificationStatus) const verificationStatus = useUserStore((state) => state.verificationStatus)
const verificationType = useUserStore((state) => state.verificationType) const verificationType = useUserStore((state) => state.verificationType)
const comment = useUserStore((state) => state.comment) const comment = useUserStore((state) => state.comment)
const userId = useUserStore((state) => state.userId) ?? "" const userId = useUserStore((state) => state.userId) ?? ""
const [onChangeTypeLP, setOnChangeTypeLP] = useState<"email" | "password" | "all" | "">("")
const [readySend, setReadySend] = useState(false)
const [loginPasswordFocus, setLoginPasswordFocus] = useState(false)
const okLP = () => {
const ready = readySend
if (ready) {
handleSendDataClick()
}
setReadySend(false)
setOnChangeTypeLP("")
}
const cancelLP = () => {
const type = onChangeTypeLP
setOnChangeTypeLP("")
setReadySend(false)
console.log(type)
if (type === "email" && user?.email !== settingsFields.email.value) setSettingsField("email", user?.email || "")
if (type === "password") setSettingsField("password", "")
if (type === "all") {
setSettingsField("email", user?.email || "")
setSettingsField("password", "")
}
}
// useEffect(() => { // useEffect(() => {
// verify(userId) // verify(userId)
// }, []) // }, [])
@ -40,6 +68,26 @@ function AccountSettings() {
bold: true, bold: true,
} }
const trySendData = () => {
console.log("клик по сохранить")
if (user?.email !== settingsFields.email.value && settingsFields.password.value.length > 0) {
setReadySend(true)
setOnChangeTypeLP("all")
return
}
if (user?.email !== settingsFields.email.value) {
setReadySend(true)
setOnChangeTypeLP("email")
return
}
if (settingsFields.password.value.length > 0) {
setReadySend(true)
setOnChangeTypeLP("password")
return
}
handleSendDataClick()
}
function handleSendDataClick() { function handleSendDataClick() {
sendUserData() sendUserData()
.then(() => { .then(() => {
@ -62,7 +110,7 @@ function AccountSettings() {
> >
<DocumentsDialog /> <DocumentsDialog />
<Typography variant="h4" mt="20px"> <Typography variant="h4" mt="20px">
Настройки аккаунта Настройки аккаунта
</Typography> </Typography>
<Box <Box
sx={{ sx={{
@ -85,7 +133,10 @@ function AccountSettings() {
flexDirection: upMd ? "row" : "column", flexDirection: upMd ? "row" : "column",
}} }}
> >
<UserFields/> <UserFields
loginPasswordFocus={loginPasswordFocus}
setLoginPasswordFocus={setLoginPasswordFocus}
/>
<Box <Box
sx={{ sx={{
maxWidth: "246px", maxWidth: "246px",
@ -102,7 +153,7 @@ function AccountSettings() {
onClick: () => openDocumentsDialog("juridical"), onClick: () => openDocumentsDialog("juridical"),
}} }}
> >
Загрузить документы для юр лиц Загрузить документы для юр лиц
</UnderlinedButtonWithIcon> </UnderlinedButtonWithIcon>
<UnderlinedButtonWithIcon <UnderlinedButtonWithIcon
icon={<UploadIcon />} icon={<UploadIcon />}
@ -111,7 +162,7 @@ function AccountSettings() {
onClick: () => openDocumentsDialog("nko"), onClick: () => openDocumentsDialog("nko"),
}} }}
> >
Загрузить документы для НКО Загрузить документы для НКО
</UnderlinedButtonWithIcon> </UnderlinedButtonWithIcon>
</> </>
)} )}
@ -123,7 +174,7 @@ function AccountSettings() {
onClick: () => openDocumentsDialog(verificationType), onClick: () => openDocumentsDialog(verificationType),
}} }}
> >
Посмотреть свою верификацию Посмотреть свою верификацию
</UnderlinedButtonWithIcon> </UnderlinedButtonWithIcon>
)} )}
{comment && <p>{comment}</p>} {comment && <p>{comment}</p>}
@ -131,13 +182,18 @@ function AccountSettings() {
</Box> </Box>
<Button <Button
variant="pena-contained-dark" variant="pena-contained-dark"
onClick={handleSendDataClick} onClick={trySendData}
disabled={fields.hasError} disabled={settingsFields.hasError || loginPasswordFocus}
sx={{ alignSelf: "end" }} sx={{ alignSelf: "end" }}
> >
Сохранить Сохранить
</Button> </Button>
</Box> </Box>
<OnChangeLoginPassword
type={onChangeTypeLP}
ok={okLP}
cancel={cancelLP}
/>
</SectionWrapper> </SectionWrapper>
) )
} }
@ -157,8 +213,8 @@ function VerificationIndicator({
verificationStatus, verificationStatus,
sx, sx,
}: { }: {
verificationStatus: VerificationStatus; verificationStatus: VerificationStatus;
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
}) { }) {
return ( return (
<Box <Box

@ -3,23 +3,29 @@ import InputTextfield from "@components/InputTextfield"
import PasswordInput from "@components/passwordInput" import PasswordInput from "@components/passwordInput"
import { setSettingsField, useUserStore } from "@root/stores/user" import { setSettingsField, useUserStore } from "@root/stores/user"
interface Props {
loginPasswordFocus: boolean;
setLoginPasswordFocus: (a:boolean) => void;
}
export default function UserFields () { export default function UserFields({
loginPasswordFocus,
setLoginPasswordFocus
}: Props) {
const theme = useTheme() const theme = useTheme()
const upSm = useMediaQuery(theme.breakpoints.up("sm")) const upSm = useMediaQuery(theme.breakpoints.up("sm"))
const upMd = useMediaQuery(theme.breakpoints.up("md")) const upMd = useMediaQuery(theme.breakpoints.up("md"))
const fields = useUserStore((state) => state.settingsFields) const { settingsFields, user } = useUserStore((state) => state)
console.log("fields")
const textFieldProps = { const textFieldProps = {
gap: upMd ? "16px" : "10px", gap: upMd ? "16px" : "10px",
color: "#F2F3F7", color: "#F2F3F7",
bold: true, bold: true,
} }
return( return (
<Box <Box
sx={{ sx={{
display: "grid", display: "grid",
@ -34,9 +40,9 @@ export default function UserFields () {
<InputTextfield <InputTextfield
TextfieldProps={{ TextfieldProps={{
placeholder: "Имя", placeholder: "Имя",
value: fields.firstname.value || "", value: settingsFields.firstname.value || "",
helperText: fields.firstname.touched && fields.firstname.error, helperText: settingsFields.firstname.touched && settingsFields.firstname.error,
error: fields.firstname.touched && Boolean(fields.firstname.error), error: settingsFields.firstname.touched && Boolean(settingsFields.firstname.error),
}} }}
onChange={(e) => setSettingsField("firstname", e.target.value)} onChange={(e) => setSettingsField("firstname", e.target.value)}
id="firstname" id="firstname"
@ -46,9 +52,9 @@ export default function UserFields () {
<InputTextfield <InputTextfield
TextfieldProps={{ TextfieldProps={{
placeholder: "Фамилия", placeholder: "Фамилия",
value: fields.secondname.value || "", value: settingsFields.secondname.value || "",
helperText: fields.secondname.touched && fields.secondname.error, helperText: settingsFields.secondname.touched && settingsFields.secondname.error,
error: fields.secondname.touched && Boolean(fields.secondname.error), error: settingsFields.secondname.touched && Boolean(settingsFields.secondname.error),
}} }}
onChange={(e) => setSettingsField("secondname", e.target.value)} onChange={(e) => setSettingsField("secondname", e.target.value)}
id="secondname" id="secondname"
@ -58,9 +64,9 @@ export default function UserFields () {
<InputTextfield <InputTextfield
TextfieldProps={{ TextfieldProps={{
placeholder: "Отчество", placeholder: "Отчество",
value: fields.middlename.value || "", value: settingsFields.middlename.value || "",
helperText: fields.middlename.touched && fields.middlename.error, helperText: settingsFields.middlename.touched && settingsFields.middlename.error,
error: fields.middlename.touched && Boolean(fields.middlename.error), error: settingsFields.middlename.touched && Boolean(settingsFields.middlename.error),
}} }}
onChange={(e) => setSettingsField("middlename", e.target.value)} onChange={(e) => setSettingsField("middlename", e.target.value)}
id="middlename" id="middlename"
@ -70,9 +76,9 @@ export default function UserFields () {
<InputTextfield <InputTextfield
TextfieldProps={{ TextfieldProps={{
placeholder: "ООО Фирма", placeholder: "ООО Фирма",
value: fields.orgname.value || "", value: settingsFields.orgname.value || user?.email || "",
helperText: fields.orgname.touched && fields.orgname.error, helperText: settingsFields.orgname.touched && settingsFields.orgname.error,
error: fields.orgname.touched && Boolean(fields.orgname.error), error: settingsFields.orgname.touched && Boolean(settingsFields.orgname.error),
}} }}
onChange={(e) => setSettingsField("orgname", e.target.value)} onChange={(e) => setSettingsField("orgname", e.target.value)}
id="orgname" id="orgname"
@ -82,21 +88,27 @@ export default function UserFields () {
<InputTextfield <InputTextfield
TextfieldProps={{ TextfieldProps={{
placeholder: "username@penahaub.com", placeholder: "username@penahaub.com",
value: fields.email.value || "", value: settingsFields.email.value || "",
helperText: fields.email.touched && fields.email.error, helperText: settingsFields.email.touched && settingsFields.email.error,
error: fields.email.touched && Boolean(fields.email.error), error: settingsFields.email.touched && Boolean(settingsFields.email.error),
}} }}
onChange={(e) => setSettingsField("email", e.target.value)} onChange={(e) => setSettingsField("email", e.target.value)}
onFocus={() => {
setLoginPasswordFocus(true)
}}
onBlur={() => {
setLoginPasswordFocus(false)
}}
id="email" id="email"
label="E-mail" label="Изменить E-mail"
{...textFieldProps} {...textFieldProps}
/> />
<InputTextfield <InputTextfield
TextfieldProps={{ TextfieldProps={{
placeholder: "+7 900 000 00 00", placeholder: "+7 900 000 00 00",
value: fields.phoneNumber.value || "", value: settingsFields.phoneNumber.value || "",
helperText: fields.phoneNumber.touched && fields.phoneNumber.error, helperText: settingsFields.phoneNumber.touched && settingsFields.phoneNumber.error,
error: fields.phoneNumber.touched && Boolean(fields.phoneNumber.error), error: settingsFields.phoneNumber.touched && Boolean(settingsFields.phoneNumber.error),
}} }}
onChange={(e) => setSettingsField("phoneNumber", e.target.value)} onChange={(e) => setSettingsField("phoneNumber", e.target.value)}
id="phoneNumber" id="phoneNumber"
@ -106,14 +118,21 @@ export default function UserFields () {
<PasswordInput <PasswordInput
TextfieldProps={{ TextfieldProps={{
placeholder: "Не менее 8 символов", placeholder: "Не менее 8 символов",
value: fields.password.value || "", value: settingsFields.password.value || "",
helperText: fields.password.touched && fields.password.error, helperText: settingsFields.password.touched && settingsFields.password.error,
error: fields.password.touched && Boolean(fields.password.error), error: settingsFields.password.touched && Boolean(settingsFields.password.error),
autoComplete: "new-password", autoComplete: "new-password",
}} }}
onFocus={() => {
setLoginPasswordFocus(true)
}}
onBlur={() => {
setLoginPasswordFocus(false)
}}
onChange={(e) => setSettingsField("password", e.target.value)} onChange={(e) => setSettingsField("password", e.target.value)}
id="password" id="password"
label="Пароль" label="Изменить пароль"
{...textFieldProps} {...textFieldProps}
/> />
</Box> </Box>

@ -0,0 +1,55 @@
import { Box, Button, Modal, Typography, useMediaQuery, useTheme } from "@mui/material"
interface Props {
type: "email" | "password" | "all" | "";
ok: () => void;
cancel: () => void;
}
export const OnChangeLoginPassword = ({
type,
ok,
cancel
}: Props) => {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down(600))
return (
<Modal
open={Boolean(type)}
>
<Box sx={{
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
bgcolor: 'background.paper',
borderRadius: "7px",
boxShadow: 24,
p: "20px 30px",
width: isMobile ? "320px" : "487px"
}}>
<Typography sx={{ textAlign: "center" }} variant="h6" component="h2">
Вы уверены, что хотите изменить
{type === "all" || type === "email" ? " почту " : ""}
{type === "all" ? "и" : ""}
{type === "all" || type === "password" ? " пароль " : ""}
?
</Typography>
<Box
sx={{
display: "flex",
justifyContent: "center",
mt: "35px",
flexWrap: isMobile ? "wrap" : "nowrap"
}}
>
<Button sx={{ m: "15px" }} variant="pena-contained-dark" onClick={cancel}>отмена</Button>
<Button sx={{ m: "15px" }} variant="pena-contained-dark" onClick={ok}>изменить</Button>
</Box>
</Box>
</Modal>
)
}

@ -15,7 +15,8 @@ const translateMessage: Record<string, string> = {
"field <phoneNumber> is empty": "Поле \"Номер телефона\" не заполнено", "field <phoneNumber> is empty": "Поле \"Номер телефона\" не заполнено",
"user with this email or login is exist": "Пользователь уже существует", "user with this email or login is exist": "Пользователь уже существует",
"user with this login is exist": "Пользователь с таким логином уже существует", "user with this login is exist": "Пользователь с таким логином уже существует",
"unauthorized": "Ссылка просрочена" "unauthorized": "Ссылка просрочена",
"insufficient funds: 910000": "Недостаточно средств"
} }
export const parseAxiosError = (nativeError: unknown): [string, number?] => { export const parseAxiosError = (nativeError: unknown): [string, number?] => {