обратная связь с плохой вёрсткой
This commit is contained in:
parent
a739bf37f2
commit
89945bae9e
56
package-lock.json
generated
56
package-lock.json
generated
@ -22,6 +22,7 @@
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dnd": "^3.0.2",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"axios": "^1.5.1",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"html-to-image": "^1.11.11",
|
||||
@ -5391,6 +5392,29 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
||||
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||
@ -14593,6 +14617,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
@ -21789,6 +21818,28 @@
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz",
|
||||
"integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
||||
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"axobject-query": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||
@ -28318,6 +28369,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dnd": "^3.0.2",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"axios": "^1.5.1",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"html-to-image": "^1.11.11",
|
||||
|
18
src/api/contactForm.ts
Normal file
18
src/api/contactForm.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
const apiUrl = "https://admin.pena.digital/feedback";
|
||||
|
||||
export function sendContactFormRequest(body: {
|
||||
|
||||
contact: string;
|
||||
whoami: string;
|
||||
}) {
|
||||
return axios(apiUrl + "/callme", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: body,
|
||||
});
|
||||
}
|
15
src/assets/icons/x.tsx
Normal file
15
src/assets/icons/x.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
|
||||
|
||||
interface Props {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export default function PenaLogo({ width }: Props) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.39844 2.39999L21.5984 21.6M2.39844 21.6L21.5984 2.39999" stroke="black"/>
|
||||
</svg>
|
||||
)}
|
@ -15,6 +15,7 @@ import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
|
||||
import { Result } from "./pages/Result/Result";
|
||||
import { Setting } from "./pages/Result/Setting";
|
||||
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
|
||||
import ContactFormModal from "@ui_kit/ContactForm";
|
||||
import ImageCrop from "@ui_kit/Modal/ImageCrop";
|
||||
import Landing from "./pages/Landing/Landing";
|
||||
|
||||
@ -37,6 +38,7 @@ const root = createRoot(document.getElementById("root")!);
|
||||
root.render(
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ContactFormModal />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
{routeslink.map((e, i) => (
|
||||
|
@ -7,7 +7,7 @@ import abstraction from '../../assets/Quiz-main.png'
|
||||
import SectionStyled from './SectionStyled';
|
||||
import {styled} from "@mui/material/styles";
|
||||
import { Link, redirect } from 'react-router-dom';
|
||||
|
||||
import { setIsContactFormOpen } from "../../stores/contactForm";
|
||||
|
||||
export default function Component() {
|
||||
return(
|
||||
@ -48,6 +48,7 @@ export default function Component() {
|
||||
</Box>
|
||||
<Link to="/list" style={{textDecoration: "none"}}>
|
||||
<Button variant="contained"
|
||||
onClick={() => setIsContactFormOpen(true)}
|
||||
>
|
||||
Попробуйте бесплатно
|
||||
|
||||
|
63
src/stores/contactForm.ts
Normal file
63
src/stores/contactForm.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { create } from "zustand";
|
||||
import { sendContactFormRequest } from "../api/contactForm";
|
||||
import { getMessageFromFetchError } from "../utils/backendMessageHandler";
|
||||
|
||||
|
||||
interface ContactFormStore {
|
||||
isModalOpen: boolean;
|
||||
isSubmitDisabled: boolean;
|
||||
mail: string;
|
||||
phoneNumberField: string;
|
||||
telegramField: string;
|
||||
whatsappField: string;
|
||||
}
|
||||
|
||||
const initialState: ContactFormStore = {
|
||||
isModalOpen: false,
|
||||
isSubmitDisabled: false,
|
||||
mail: "",
|
||||
phoneNumberField: "",
|
||||
telegramField: "",
|
||||
whatsappField: "",
|
||||
};
|
||||
|
||||
export const useContactFormStore = create<ContactFormStore>()(
|
||||
(set, get) => initialState
|
||||
);
|
||||
|
||||
export const setIsContactFormOpen = (isModalOpen: ContactFormStore["isModalOpen"]) => useContactFormStore.setState({ isModalOpen });
|
||||
export const setContactFormMailField = (mail: ContactFormStore["mail"]) => useContactFormStore.setState({ mail });
|
||||
export const setContactFormTelegramField = (telegramField: ContactFormStore["telegramField"]) => useContactFormStore.setState({ telegramField });
|
||||
export const setIsSubmitDisabled = (isSubmitDisabled: ContactFormStore["isSubmitDisabled"]) => useContactFormStore.setState({ isSubmitDisabled });
|
||||
export const setContactFormPhoneNumberField = (phoneNumberField: ContactFormStore["phoneNumberField"]) => {
|
||||
if (/^\+?\d*$/.test(phoneNumberField)) useContactFormStore.setState({ phoneNumberField });
|
||||
};
|
||||
export const setContactFormWhatsappField = (whatsappField: ContactFormStore["whatsappField"]) => {
|
||||
if (/^\+?\d*$/.test(whatsappField)) useContactFormStore.setState({ whatsappField });
|
||||
};
|
||||
|
||||
export const sendContactForm = async (): Promise<string | null> => {
|
||||
const { mail, phoneNumberField, telegramField, whatsappField } = useContactFormStore.getState();
|
||||
|
||||
if (!mail) return "Почта не указана";
|
||||
let contact = `phonenumber:${phoneNumberField}`;
|
||||
if (telegramField) contact += `\ntelegram:${telegramField}`;
|
||||
if (whatsappField) contact += `\nwhatsapp:${whatsappField}`;
|
||||
|
||||
try {
|
||||
useContactFormStore.setState({ isSubmitDisabled: true });
|
||||
const response = await sendContactFormRequest({
|
||||
contact,
|
||||
whoami: mail,
|
||||
});
|
||||
|
||||
if (response.status !== 200) throw new Error(response.statusText);
|
||||
|
||||
useContactFormStore.setState({ ...initialState });
|
||||
|
||||
return "Данные отправлены";
|
||||
} catch (error: any) {
|
||||
useContactFormStore.setState({ isSubmitDisabled: false });
|
||||
return getMessageFromFetchError(error);
|
||||
}
|
||||
};
|
132
src/ui_kit/ContactForm.tsx
Normal file
132
src/ui_kit/ContactForm.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import { Box, Button, IconButton, TextField, Typography, useMediaQuery, useTheme, Modal } from "@mui/material";
|
||||
import {
|
||||
sendContactForm,
|
||||
setContactFormMailField,
|
||||
setIsContactFormOpen,
|
||||
useContactFormStore,
|
||||
} from "../stores/contactForm";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import X from "../assets/icons/x";
|
||||
import PenaLogo from "../ui_kit/PenaLogo";
|
||||
|
||||
export default () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||
|
||||
const isModalOpen = useContactFormStore((state) => state.isModalOpen);
|
||||
const isSubmitDisabled = useContactFormStore((state) => state.isSubmitDisabled);
|
||||
const mail = useContactFormStore((state) => state.mail);
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
async function handleSendForm() {
|
||||
const message = await sendContactForm();
|
||||
|
||||
if (message) enqueueSnackbar(message);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
disableScrollLock
|
||||
open={isModalOpen}
|
||||
onClose={() => setIsContactFormOpen(false)}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
sx={{
|
||||
width: "600px",
|
||||
height: "474px",
|
||||
margin: "200px auto 0",
|
||||
position: "relative",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
p: upMd ? "50px" : "18px",
|
||||
pb: upMd ? "40px" : "30px",
|
||||
gap: "15px",
|
||||
borderRadius: "12px",
|
||||
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled":
|
||||
{
|
||||
position: "absolute",
|
||||
top: "46px",
|
||||
margin: "0",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => setIsContactFormOpen(false)}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: "7px",
|
||||
top: "7px",
|
||||
}}
|
||||
>
|
||||
<X/>
|
||||
</IconButton>
|
||||
<Box>
|
||||
<PenaLogo width={upMd ? 233 : 196} />
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
mt: "5px",
|
||||
mb: upMd ? "10px" : "33px",
|
||||
}}
|
||||
>
|
||||
Предрегистрация
|
||||
</Typography>
|
||||
<TextField
|
||||
value={mail}
|
||||
onChange={(e) => setContactFormMailField(e.target.value)}
|
||||
placeholder="Имя"
|
||||
name="name"
|
||||
fullWidth
|
||||
sx={{
|
||||
width: isMobile ? "266px" : "300px",
|
||||
mt: "25px",
|
||||
mb: "10px",
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
height: "45px",
|
||||
borderRadius: "10px",
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
fullWidth
|
||||
onClick={handleSendForm}
|
||||
sx={{
|
||||
py: "12px",
|
||||
"&:hover": {
|
||||
},
|
||||
"&:active": {
|
||||
color: "white",
|
||||
backgroundColor: "black",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Отправить
|
||||
</Button>
|
||||
<Typography
|
||||
sx={{
|
||||
mt: "5px",
|
||||
mb: upMd ? "10px" : "33px",
|
||||
}}
|
||||
>
|
||||
После запуска продукта вам придет сообщение
|
||||
на указанную электронную почту
|
||||
</Typography>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
35
src/utils/backendMessageHandler.ts
Normal file
35
src/utils/backendMessageHandler.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { isAxiosError } from "axios";
|
||||
|
||||
const backendErrorMessage: Record<string, string> = {
|
||||
"user not found": "Пользователь не найден",
|
||||
"invalid password": "Неправильный пароль",
|
||||
"field <password> is empty": 'Поле "Пароль" не заполнено',
|
||||
"field <login> is empty": 'Поле "Логин" не заполнено',
|
||||
"field <email> is empty": 'Поле "E-mail" не заполнено',
|
||||
"field <phoneNumber> is empty": 'Поле "Номер телефона" не заполнено',
|
||||
"user with this email or login is exist": "Пользователь уже существует",
|
||||
};
|
||||
|
||||
const unknownErrorMessage = "Что-то пошло не так. Повторите попытку позже";
|
||||
|
||||
export function getMessageFromFetchError(error: any): string | null {
|
||||
if (process.env.NODE_ENV !== "production") console.log(error);
|
||||
|
||||
const message = backendErrorMessage[error.response?.data?.message];
|
||||
if (message) return message;
|
||||
|
||||
if (error.message === "Failed to fetch") return "Ошибка сети";
|
||||
|
||||
if (isAxiosError(error)) {
|
||||
switch (error.code) {
|
||||
case "ERR_NETWORK":
|
||||
return "Ошибка сети";
|
||||
case "ERR_CANCELED":
|
||||
return null;
|
||||
case "ERR_BAD_REQUEST":
|
||||
return "Слишком много запросов";
|
||||
}
|
||||
}
|
||||
|
||||
return unknownErrorMessage;
|
||||
}
|
Loading…
Reference in New Issue
Block a user