Создание тарифа на бэкенде

This commit is contained in:
ArtChaos189 2023-06-07 15:46:56 +03:00
parent 9747e18c0a
commit 2aa014aefc
7 changed files with 408 additions and 317 deletions

@ -11,164 +11,167 @@ import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
import { authStore } from "@root/stores/auth";
interface Values {
email: string;
password: string;
email: string;
password: string;
}
function validate(values: Values) {
const errors = {} as any;
const errors = {} as any;
if (!values.email) {
errors.email = "Required";
}
if (!values.email) {
errors.email = "Required";
}
if (!values.password) {
errors.password = "Required";
}
if (!values.password) {
errors.password = "Required";
}
if (values.password && !/^[\S]{8,25}$/i.test(values.password)) {
errors.password = "Invalid password";
}
if (values.password && !/^[\S]{8,25}$/i.test(values.password)) {
errors.password = "Invalid password";
}
return errors;
return errors;
}
const SigninForm = () => {
const theme = useTheme();
const navigate = useNavigate();
const theme = useTheme();
const navigate = useNavigate();
const { makeRequest } = authStore();
const { makeRequest } = authStore();
const initialValues: Values = {
email: "",
password: "",
};
const initialValues: Values = {
email: "",
password: "",
};
const onSignFormSubmit = (values: Values, formikHelpers: FormikHelpers<Values>) => {
formikHelpers.setSubmitting(true);
makeRequest({
url: "https://admin.pena.digital/auth/login",
body: {
email: values.email,
password: values.password,
},
useToken: false,
})
.then((e) => {
navigate("/users");
})
.catch((e) => {
console.log(e);
enqueueSnackbar(e.message ? e.message : `Unknown error`);
}).finally(() => {
formikHelpers.setSubmitting(false);
});
};
const onSignFormSubmit = (values: Values, formikHelpers: FormikHelpers<Values>) => {
formikHelpers.setSubmitting(true);
makeRequest({
url: "https://admin.pena.digital/auth/login",
body: {
login: values.email,
password: values.password,
},
useToken: false,
})
.then((e) => {
navigate("/users");
})
.catch((e) => {
console.log(e);
enqueueSnackbar(e.message ? e.message : `Unknown error`);
})
.finally(() => {
formikHelpers.setSubmitting(false);
});
};
return (
<Formik initialValues={initialValues} validate={validate} onSubmit={onSignFormSubmit}>
{props =>
<Form>
<Box
component="section"
sx={{
minHeight: "100vh",
height: "100%",
width: "100%",
backgroundColor: theme.palette.content.main,
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "15px 0",
}}
>
<Box
component="article"
sx={{
width: "350px",
backgroundColor: theme.palette.content.main,
display: "flex",
flexDirection: "column",
justifyContent: "center",
"> *": {
marginTop: "15px",
},
}}
>
<Logo />
<Box>
<Typography variant="h5" color={theme.palette.secondary.main}>
Добро пожаловать
</Typography>
<Typography variant="h6" color={theme.palette.secondary.main}>
Мы рады что вы выбрали нас!
</Typography>
</Box>
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта" />
</Box>
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль" />
</Box>
<Box
component="article"
sx={{
display: "flex",
alignItems: "center",
}}
>
<FormControlLabel
sx={{ color: "white" }}
control={
<Checkbox
value="checkedA"
inputProps={{ "aria-label": "Checkbox A" }}
sx={{
color: "white",
transform: "scale(1.5)",
"&.Mui-checked": {
color: "white",
},
"&.MuiFormControlLabel-root": {
color: "white",
},
}}
/>
}
label="Запомнить этот компьютер"
/>
</Box>
<Link to="/restore" style={{ textDecoration: "none" }}>
<Typography color={theme.palette.golden.main}>Забыли пароль?</Typography>
</Link>
<Button
type="submit"
disabled={props.isSubmitting}
sx={{
width: "250px",
margin: "15px auto",
padding: "20px 30px",
fontSize: 18,
}}
>Войти</Button>
<Box
sx={{
display: "flex",
}}
>
<Typography color={theme.palette.secondary.main}>У вас нет аккаунта?&nbsp;</Typography>
<Link to="/signup" style={{ textDecoration: "none" }}>
<Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography>
</Link>
</Box>
</Box>
</Box>
</Form>
}
</Formik>
);
return (
<Formik initialValues={initialValues} validate={validate} onSubmit={onSignFormSubmit}>
{(props) => (
<Form>
<Box
component="section"
sx={{
minHeight: "100vh",
height: "100%",
width: "100%",
backgroundColor: theme.palette.content.main,
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "15px 0",
}}
>
<Box
component="article"
sx={{
width: "350px",
backgroundColor: theme.palette.content.main,
display: "flex",
flexDirection: "column",
justifyContent: "center",
"> *": {
marginTop: "15px",
},
}}
>
<Logo />
<Box>
<Typography variant="h5" color={theme.palette.secondary.main}>
Добро пожаловать
</Typography>
<Typography variant="h6" color={theme.palette.secondary.main}>
Мы рады что вы выбрали нас!
</Typography>
</Box>
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта" />
</Box>
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
<LockOutlinedIcon htmlColor={theme.palette.golden.main} />
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль" />
</Box>
<Box
component="article"
sx={{
display: "flex",
alignItems: "center",
}}
>
<FormControlLabel
sx={{ color: "white" }}
control={
<Checkbox
value="checkedA"
inputProps={{ "aria-label": "Checkbox A" }}
sx={{
color: "white",
transform: "scale(1.5)",
"&.Mui-checked": {
color: "white",
},
"&.MuiFormControlLabel-root": {
color: "white",
},
}}
/>
}
label="Запомнить этот компьютер"
/>
</Box>
<Link to="/restore" style={{ textDecoration: "none" }}>
<Typography color={theme.palette.golden.main}>Забыли пароль?</Typography>
</Link>
<Button
type="submit"
disabled={props.isSubmitting}
sx={{
width: "250px",
margin: "15px auto",
padding: "20px 30px",
fontSize: 18,
}}
>
Войти
</Button>
<Box
sx={{
display: "flex",
}}
>
<Typography color={theme.palette.secondary.main}>У вас нет аккаунта?&nbsp;</Typography>
<Link to="/signup" style={{ textDecoration: "none" }}>
<Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography>
</Link>
</Box>
</Box>
</Box>
</Form>
)}
</Formik>
);
};
export default SigninForm;

@ -2,111 +2,134 @@ import { Box, Checkbox, TextField, Typography, useTheme } from "@mui/material";
import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker";
import { useState } from "react";
export default function DatePickers() {
const theme = useTheme();
const [isInfinite, setIsInfinite] = useState<boolean>(false);
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
const theme = useTheme();
const [isInfinite, setIsInfinite] = useState<boolean>(false);
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
return (
<>
<Typography
variant="h4"
sx={{
width: "90%",
height: "40px",
fontWeight: "normal",
color: theme.palette.grayDisabled.main,
marginTop: "55px"
}}>
Дата действия:
</Typography>
<Box sx={{
width: "100%",
display: "flex",
flexWrap: 'wrap'
}}>
<Typography sx={{
width: "35px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "left",
}}>С</Typography>
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={startDate}
onChange={(e) => { if (e) { setStartDate(e); } }}
renderInput={(params) => <TextField {...params} />}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
}
}}
/>
<Typography sx={{
width: "65px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}>по</Typography>
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={endDate}
onChange={(e) => { if (e) { setEndDate(e); } }}
renderInput={(params) => <TextField {...params} />}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main }
}
}}
/>
</Box>
<Box sx={{
display: "flex",
width: "90%",
marginTop: theme.spacing(2),
}}>
<Box sx={{
width: "20px",
height: "42px",
display: "flex",
flexDirection: "column",
justifyContent: "left",
alignItems: "left",
marginRight: theme.spacing(1)
}}>
<Checkbox
sx={{
color: theme.palette.secondary.main,
"&.Mui-checked": {
color: theme.palette.secondary.main,
},
}}
checked={isInfinite}
onClick={() => setIsInfinite(p => !p)}
/>
</Box>
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}>
Бессрочно
</Box>
</Box>
</>
);
};
return (
<>
<Typography
variant="h4"
sx={{
width: "90%",
height: "40px",
fontWeight: "normal",
color: theme.palette.grayDisabled.main,
marginTop: "55px",
}}
>
Дата действия:
</Typography>
<Box
sx={{
width: "100%",
display: "flex",
flexWrap: "wrap",
}}
>
<Typography
sx={{
width: "35px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "left",
}}
>
С
</Typography>
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={startDate}
onChange={(e) => {
if (e) {
setStartDate(e);
}
}}
renderInput={(params) => <TextField {...params} />}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main },
},
}}
/>
<Typography
sx={{
width: "65px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
по
</Typography>
<DesktopDatePicker
inputFormat="DD/MM/YYYY"
value={endDate}
onChange={(e) => {
if (e) {
setEndDate(e);
}
}}
renderInput={(params) => <TextField {...params} />}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main },
},
}}
/>
</Box>
<Box
sx={{
display: "flex",
width: "90%",
marginTop: theme.spacing(2),
}}
>
<Box
sx={{
width: "20px",
height: "42px",
display: "flex",
flexDirection: "column",
justifyContent: "left",
alignItems: "left",
marginRight: theme.spacing(1),
}}
>
<Checkbox
sx={{
color: theme.palette.secondary.main,
"&.Mui-checked": {
color: theme.palette.secondary.main,
},
}}
checked={isInfinite}
onClick={() => setIsInfinite((p) => !p)}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
Бессрочно
</Box>
</Box>
</>
);
}

@ -1,10 +1,17 @@
import { Typography, Container, Button, Select, MenuItem, FormControl, InputLabel, useTheme, Box } from "@mui/material";
import { useCombinedPrivileges } from "@root/hooks/useCombinedPrivileges.hook";
import { CustomTextField } from "@root/kitUI/CustomTextField";
import { Tariff } from "@root/model/tariff";
import { addTariffs } from "@root/stores/tariffs";
import { nanoid } from "nanoid";
import { useState } from "react";
import { Typography, Container, Button, Select, MenuItem, FormControl, InputLabel, useTheme, Box } from "@mui/material";
import { nanoid } from "nanoid";
import { enqueueSnackbar } from "notistack";
import axios from "axios";
import { CustomTextField } from "@root/kitUI/CustomTextField";
import { useCombinedPrivileges } from "@root/hooks/useCombinedPrivileges.hook";
import { Tariff } from "@root/model/tariff";
import { addTariffs } from "@root/stores/tariffs";
import { authStore } from "@root/stores/auth";
export default function CreateTariff() {
const theme = useTheme();
@ -13,6 +20,7 @@ export default function CreateTariff() {
const [customPriceField, setCustomPriceField] = useState<string>("");
const [privilegeIdField, setPrivilegeIdField] = useState<string>("");
const { mergedPrivileges, isError, errorMessage } = useCombinedPrivileges();
const { token } = authStore();
const findPrivilegeById = (privilegeId: string) => {
return mergedPrivileges.find((privilege) => privilege.privilegeId === privilegeId) ?? null;
@ -20,7 +28,19 @@ export default function CreateTariff() {
const privilege = findPrivilegeById(privilegeIdField);
console.log(privilege);
function handleCreateTariffClick() {
if (nameField === "") {
enqueueSnackbar("Пустое название тарифа");
}
if (amountField === "") {
enqueueSnackbar("Пустое кол-во едениц привилегия");
}
if (privilegeIdField === "") {
enqueueSnackbar("Невыбрана привилегия");
}
const amount = Number(amountField);
const customPrice = Number(customPriceField);
@ -30,15 +50,57 @@ export default function CreateTariff() {
id: nanoid(5),
name: nameField,
amount,
privilegeId: privilege.privilegeId,
customPricePerUnit: customPrice ? customPrice / amount : undefined,
};
addTariffs([newTariff]);
console.log(newTariff);
axios({})
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.error(error);
});
}
const createTariff = async () => {
try {
if (!privilege) {
throw new Error("Привилегия не выбрана");
}
const { data } = await axios({
url: "https://admin.pena.digital/strator/tariff/",
method: "post",
headers: {
Authorization: `Bearer ${token}`,
},
data: {
name: nameField,
price: Number(customPriceField) * 100,
isCustom: false,
privilegies: [
{
name: privilege.name,
privilegeId: privilege.privilegeId,
serviceKey: privilege.serviceKey,
description: privilege.description,
type: privilege.type,
value: privilege.value,
price: privilege.price,
amount: amountField,
},
],
},
});
} catch (error) {
enqueueSnackbar((error as Error).message);
}
};
return (
<Container
sx={{
@ -74,7 +136,7 @@ export default function CreateTariff() {
lineHeight: "19px",
}}
>
Привелегия
Привилегия
</InputLabel>
{isError ? (
<Typography>{errorMessage}</Typography>
@ -148,8 +210,11 @@ export default function CreateTariff() {
type="number"
/>
<Button
onClick={handleCreateTariffClick}
disabled={privilegeIdField === "" || amountField === "" || nameField === ""}
onClick={() => {
handleCreateTariffClick();
createTariff();
}}
// disabled={privilegeIdField === "" || amountField === "" || nameField === ""}
>
Создать
</Button>

@ -10,27 +10,27 @@ import CreateTariff from "./CreateTariff";
import ChangePriceModal from "./Privileges/ChangePriceModal";
export default function Tariffs() {
const [selectedTariffs, setSelectedTariffs] = useState<GridSelectionModel>([]);
const [selectedTariffs, setSelectedTariffs] = useState<GridSelectionModel>([]);
return (
<Container
sx={{
width: "90%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
<Typography variant="h6">Список привелегий</Typography>
<Privileges />
<ChangePriceModal />
<CreateTariff />
<Typography variant="h6" mt="20px">
Список тарифов
</Typography>
<TariffsDG handleSelectionChange={(selectionModel) => setSelectedTariffs(selectionModel)} />
<Cart selectedTariffs={selectedTariffs} />
</Container>
);
return (
<Container
sx={{
width: "90%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
<Typography variant="h6">Список привелегий</Typography>
<Privileges />
<ChangePriceModal />
<CreateTariff />
<Typography variant="h6" mt="20px">
Список тарифов
</Typography>
<TariffsDG handleSelectionChange={(selectionModel) => setSelectedTariffs(selectionModel)} />
<Cart selectedTariffs={selectedTariffs} />
</Container>
);
}

@ -16,6 +16,8 @@ interface Props {
export default function TariffsDG({ handleSelectionChange }: Props) {
const tariffs = useTariffStore((state) => state.tariffs);
console.log(tariffs);
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 100 },
{ field: "name", headerName: "Название тарифа", width: 150 },
@ -34,7 +36,6 @@ export default function TariffsDG({ handleSelectionChange }: Props) {
return (
<IconButton
onClick={() => {
console.log(row.id);
deleteTariffs(row.id);
}}
>

@ -28,7 +28,7 @@ export const exampleCartValues: ExampleCartValues = {
{
serviceKey: "templategen",
name: "unlim",
privilegeId: "1",
privilegeId: "6460f329472fe2d77d5ee661",
description: "привилегия безлимитного доступа к шаблонизатору на время. в днях",
type: "day",
price: 0.5,
@ -36,7 +36,7 @@ export const exampleCartValues: ExampleCartValues = {
{
serviceKey: "templategen",
name: "gencount",
privilegeId: "2",
privilegeId: "6460f329472fe2d77d5ee662",
description: "привилегия на определённое количество генераций",
type: "count",
price: 0.1,
@ -44,7 +44,7 @@ export const exampleCartValues: ExampleCartValues = {
{
serviceKey: "squiz",
name: "unlim",
privilegeId: "3",
privilegeId: "6460f329472fe2d77d5ee663",
description: "привилегия безлимитного доступа к опроснику. в днях",
type: "day",
price: 3.0,
@ -52,7 +52,7 @@ export const exampleCartValues: ExampleCartValues = {
{
serviceKey: "squiz",
name: "activequiz",
privilegeId: "4",
privilegeId: "6460f329472fe2d77d5ee664",
description: "привилегия создания ограниченного количества опросов",
type: "count",
price: 1.0,
@ -60,7 +60,7 @@ export const exampleCartValues: ExampleCartValues = {
{
serviceKey: "dwarfener",
name: "unlim",
privilegeId: "5",
privilegeId: "6460f329472fe2d77d5ee665",
description: "привилегия безлимитного доступа к сокращателю на время. в днях",
type: "day",
price: 0.1,
@ -68,7 +68,7 @@ export const exampleCartValues: ExampleCartValues = {
{
serviceKey: "dwarfener",
name: "abcount",
privilegeId: "6",
privilegeId: "6460f329472fe2d77d5ee666",
description: "привилегия на количество активных ссылок в абтестах",
type: "count",
price: 0.7,
@ -76,7 +76,7 @@ export const exampleCartValues: ExampleCartValues = {
{
serviceKey: "dwarfener",
name: "extended",
privilegeId: "7",
privilegeId: "6460f329472fe2d77d5ee667",
description: "привилегия расширенной статистики, в днях",
type: "day",
price: 2,

@ -1,30 +1,29 @@
import { Tariff } from "@root/model/tariff";
export const exampleTariffs: Tariff[] = [
{
id: "tariffId1",
name: "Tariff 1",
privilegeId: "p1",
amount: 1000,
},
{
id: "tariffId2",
name: "Tariff 2",
privilegeId: "p2",
amount: 2000,
customPricePerUnit: 3,
},
{
id: "tariffId3",
name: "Tariff 3",
privilegeId: "p3",
amount: 3000,
},
{
id: "tariffId4",
name: "Tariff 4",
privilegeId: "p6",
amount: 4000,
},
];
{
id: "tariffId1",
name: "Tariff 1",
privilegeId: "p1",
amount: 1000,
},
{
id: "tariffId2",
name: "Tariff 2",
privilegeId: "p2",
amount: 2000,
customPricePerUnit: 3,
},
{
id: "tariffId3",
name: "Tariff 3",
privilegeId: "p3",
amount: 3000,
},
{
id: "tariffId4",
name: "Tariff 4",
privilegeId: "p6",
amount: 4000,
},
];