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

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"; import { authStore } from "@root/stores/auth";
interface Values { interface Values {
email: string; email: string;
password: string; password: string;
} }
function validate(values: Values) { function validate(values: Values) {
const errors = {} as any; const errors = {} as any;
if (!values.email) { if (!values.email) {
errors.email = "Required"; errors.email = "Required";
} }
if (!values.password) { if (!values.password) {
errors.password = "Required"; errors.password = "Required";
} }
if (values.password && !/^[\S]{8,25}$/i.test(values.password)) { if (values.password && !/^[\S]{8,25}$/i.test(values.password)) {
errors.password = "Invalid password"; errors.password = "Invalid password";
} }
return errors; return errors;
} }
const SigninForm = () => { const SigninForm = () => {
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const { makeRequest } = authStore(); const { makeRequest } = authStore();
const initialValues: Values = { const initialValues: Values = {
email: "", email: "",
password: "", password: "",
}; };
const onSignFormSubmit = (values: Values, formikHelpers: FormikHelpers<Values>) => { const onSignFormSubmit = (values: Values, formikHelpers: FormikHelpers<Values>) => {
formikHelpers.setSubmitting(true); formikHelpers.setSubmitting(true);
makeRequest({ makeRequest({
url: "https://admin.pena.digital/auth/login", url: "https://admin.pena.digital/auth/login",
body: { body: {
email: values.email, login: values.email,
password: values.password, password: values.password,
}, },
useToken: false, useToken: false,
}) })
.then((e) => { .then((e) => {
navigate("/users"); navigate("/users");
}) })
.catch((e) => { .catch((e) => {
console.log(e); console.log(e);
enqueueSnackbar(e.message ? e.message : `Unknown error`); enqueueSnackbar(e.message ? e.message : `Unknown error`);
}).finally(() => { })
formikHelpers.setSubmitting(false); .finally(() => {
}); formikHelpers.setSubmitting(false);
}; });
};
return ( return (
<Formik initialValues={initialValues} validate={validate} onSubmit={onSignFormSubmit}> <Formik initialValues={initialValues} validate={validate} onSubmit={onSignFormSubmit}>
{props => {(props) => (
<Form> <Form>
<Box <Box
component="section" component="section"
sx={{ sx={{
minHeight: "100vh", minHeight: "100vh",
height: "100%", height: "100%",
width: "100%", width: "100%",
backgroundColor: theme.palette.content.main, backgroundColor: theme.palette.content.main,
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
padding: "15px 0", padding: "15px 0",
}} }}
> >
<Box <Box
component="article" component="article"
sx={{ sx={{
width: "350px", width: "350px",
backgroundColor: theme.palette.content.main, backgroundColor: theme.palette.content.main,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
justifyContent: "center", justifyContent: "center",
"> *": { "> *": {
marginTop: "15px", marginTop: "15px",
}, },
}} }}
> >
<Logo /> <Logo />
<Box> <Box>
<Typography variant="h5" color={theme.palette.secondary.main}> <Typography variant="h5" color={theme.palette.secondary.main}>
Добро пожаловать Добро пожаловать
</Typography> </Typography>
<Typography variant="h6" color={theme.palette.secondary.main}> <Typography variant="h6" color={theme.palette.secondary.main}>
Мы рады что вы выбрали нас! Мы рады что вы выбрали нас!
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}> <Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
<EmailOutlinedIcon htmlColor={theme.palette.golden.main} /> <EmailOutlinedIcon htmlColor={theme.palette.golden.main} />
<Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта" /> <Field as={OutlinedInput} name="email" variant="filled" label="Эл. почта" />
</Box> </Box>
<Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}> <Box sx={{ display: "flex", alignItems: "center", marginTop: "15px", "> *": { marginRight: "10px" } }}>
<LockOutlinedIcon htmlColor={theme.palette.golden.main} /> <LockOutlinedIcon htmlColor={theme.palette.golden.main} />
<Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль" /> <Field as={OutlinedInput} type="password" name="password" variant="filled" label="Пароль" />
</Box> </Box>
<Box <Box
component="article" component="article"
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
}} }}
> >
<FormControlLabel <FormControlLabel
sx={{ color: "white" }} sx={{ color: "white" }}
control={ control={
<Checkbox <Checkbox
value="checkedA" value="checkedA"
inputProps={{ "aria-label": "Checkbox A" }} inputProps={{ "aria-label": "Checkbox A" }}
sx={{ sx={{
color: "white", color: "white",
transform: "scale(1.5)", transform: "scale(1.5)",
"&.Mui-checked": { "&.Mui-checked": {
color: "white", color: "white",
}, },
"&.MuiFormControlLabel-root": { "&.MuiFormControlLabel-root": {
color: "white", color: "white",
}, },
}} }}
/> />
} }
label="Запомнить этот компьютер" label="Запомнить этот компьютер"
/> />
</Box> </Box>
<Link to="/restore" style={{ textDecoration: "none" }}> <Link to="/restore" style={{ textDecoration: "none" }}>
<Typography color={theme.palette.golden.main}>Забыли пароль?</Typography> <Typography color={theme.palette.golden.main}>Забыли пароль?</Typography>
</Link> </Link>
<Button <Button
type="submit" type="submit"
disabled={props.isSubmitting} disabled={props.isSubmitting}
sx={{ sx={{
width: "250px", width: "250px",
margin: "15px auto", margin: "15px auto",
padding: "20px 30px", padding: "20px 30px",
fontSize: 18, fontSize: 18,
}} }}
>Войти</Button> >
<Box Войти
sx={{ </Button>
display: "flex", <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> <Typography color={theme.palette.secondary.main}>У вас нет аккаунта?&nbsp;</Typography>
</Link> <Link to="/signup" style={{ textDecoration: "none" }}>
</Box> <Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography>
</Box> </Link>
</Box> </Box>
</Form> </Box>
} </Box>
</Formik> </Form>
); )}
</Formik>
);
}; };
export default SigninForm; 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 { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker";
import { useState } from "react"; import { useState } from "react";
export default function DatePickers() { export default function DatePickers() {
const theme = useTheme(); const theme = useTheme();
const [isInfinite, setIsInfinite] = useState<boolean>(false); const [isInfinite, setIsInfinite] = useState<boolean>(false);
const [startDate, setStartDate] = useState<Date>(new Date()); const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date()); const [endDate, setEndDate] = useState<Date>(new Date());
return (
return ( <>
<> <Typography
<Typography variant="h4"
variant="h4" sx={{
sx={{ width: "90%",
width: "90%", height: "40px",
height: "40px", fontWeight: "normal",
fontWeight: "normal", color: theme.palette.grayDisabled.main,
color: theme.palette.grayDisabled.main, marginTop: "55px",
marginTop: "55px" }}
}}> >
Дата действия: Дата действия:
</Typography> </Typography>
<Box sx={{ <Box
width: "100%", sx={{
display: "flex", width: "100%",
flexWrap: 'wrap' display: "flex",
}}> flexWrap: "wrap",
<Typography sx={{ }}
width: "35px", >
display: "flex", <Typography
flexDirection: "column", sx={{
justifyContent: "center", width: "35px",
alignItems: "left", display: "flex",
}}>С</Typography> flexDirection: "column",
<DesktopDatePicker justifyContent: "center",
inputFormat="DD/MM/YYYY" alignItems: "left",
value={startDate} }}
onChange={(e) => { if (e) { setStartDate(e); } }} >
renderInput={(params) => <TextField {...params} />} С
InputProps={{ </Typography>
sx: { <DesktopDatePicker
height: "40px", inputFormat="DD/MM/YYYY"
color: theme.palette.secondary.main, value={startDate}
border: "1px solid", onChange={(e) => {
borderColor: theme.palette.secondary.main, if (e) {
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main } setStartDate(e);
} }
}} }}
/> renderInput={(params) => <TextField {...params} />}
<Typography sx={{ InputProps={{
width: "65px", sx: {
display: "flex", height: "40px",
flexDirection: "column", color: theme.palette.secondary.main,
justifyContent: "center", border: "1px solid",
alignItems: "center", borderColor: theme.palette.secondary.main,
}}>по</Typography> "& .MuiSvgIcon-root": { color: theme.palette.secondary.main },
<DesktopDatePicker },
inputFormat="DD/MM/YYYY" }}
value={endDate} />
onChange={(e) => { if (e) { setEndDate(e); } }} <Typography
renderInput={(params) => <TextField {...params} />} sx={{
InputProps={{ width: "65px",
sx: { display: "flex",
height: "40px", flexDirection: "column",
color: theme.palette.secondary.main, justifyContent: "center",
border: "1px solid", alignItems: "center",
borderColor: theme.palette.secondary.main, }}
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main } >
} по
}} </Typography>
/> <DesktopDatePicker
</Box> inputFormat="DD/MM/YYYY"
<Box sx={{ value={endDate}
display: "flex", onChange={(e) => {
width: "90%", if (e) {
marginTop: theme.spacing(2), setEndDate(e);
}}> }
<Box sx={{ }}
width: "20px", renderInput={(params) => <TextField {...params} />}
height: "42px", InputProps={{
display: "flex", sx: {
flexDirection: "column", height: "40px",
justifyContent: "left", color: theme.palette.secondary.main,
alignItems: "left", border: "1px solid",
marginRight: theme.spacing(1) borderColor: theme.palette.secondary.main,
}}> "& .MuiSvgIcon-root": { color: theme.palette.secondary.main },
<Checkbox },
sx={{ }}
color: theme.palette.secondary.main, />
"&.Mui-checked": { </Box>
color: theme.palette.secondary.main, <Box
}, sx={{
}} display: "flex",
checked={isInfinite} width: "90%",
onClick={() => setIsInfinite(p => !p)} marginTop: theme.spacing(2),
/> }}
</Box> >
<Box sx={{ <Box
display: "flex", sx={{
flexDirection: "column", width: "20px",
justifyContent: "center", height: "42px",
alignItems: "center" display: "flex",
}}> flexDirection: "column",
Бессрочно justifyContent: "left",
</Box> alignItems: "left",
</Box> 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 { 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() { export default function CreateTariff() {
const theme = useTheme(); const theme = useTheme();
@ -13,6 +20,7 @@ export default function CreateTariff() {
const [customPriceField, setCustomPriceField] = useState<string>(""); const [customPriceField, setCustomPriceField] = useState<string>("");
const [privilegeIdField, setPrivilegeIdField] = useState<string>(""); const [privilegeIdField, setPrivilegeIdField] = useState<string>("");
const { mergedPrivileges, isError, errorMessage } = useCombinedPrivileges(); const { mergedPrivileges, isError, errorMessage } = useCombinedPrivileges();
const { token } = authStore();
const findPrivilegeById = (privilegeId: string) => { const findPrivilegeById = (privilegeId: string) => {
return mergedPrivileges.find((privilege) => privilege.privilegeId === privilegeId) ?? null; return mergedPrivileges.find((privilege) => privilege.privilegeId === privilegeId) ?? null;
@ -20,7 +28,19 @@ export default function CreateTariff() {
const privilege = findPrivilegeById(privilegeIdField); const privilege = findPrivilegeById(privilegeIdField);
console.log(privilege);
function handleCreateTariffClick() { function handleCreateTariffClick() {
if (nameField === "") {
enqueueSnackbar("Пустое название тарифа");
}
if (amountField === "") {
enqueueSnackbar("Пустое кол-во едениц привилегия");
}
if (privilegeIdField === "") {
enqueueSnackbar("Невыбрана привилегия");
}
const amount = Number(amountField); const amount = Number(amountField);
const customPrice = Number(customPriceField); const customPrice = Number(customPriceField);
@ -30,15 +50,57 @@ export default function CreateTariff() {
id: nanoid(5), id: nanoid(5),
name: nameField, name: nameField,
amount, amount,
privilegeId: privilege.privilegeId, privilegeId: privilege.privilegeId,
customPricePerUnit: customPrice ? customPrice / amount : undefined, customPricePerUnit: customPrice ? customPrice / amount : undefined,
}; };
addTariffs([newTariff]); 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 ( return (
<Container <Container
sx={{ sx={{
@ -74,7 +136,7 @@ export default function CreateTariff() {
lineHeight: "19px", lineHeight: "19px",
}} }}
> >
Привелегия Привилегия
</InputLabel> </InputLabel>
{isError ? ( {isError ? (
<Typography>{errorMessage}</Typography> <Typography>{errorMessage}</Typography>
@ -148,8 +210,11 @@ export default function CreateTariff() {
type="number" type="number"
/> />
<Button <Button
onClick={handleCreateTariffClick} onClick={() => {
disabled={privilegeIdField === "" || amountField === "" || nameField === ""} handleCreateTariffClick();
createTariff();
}}
// disabled={privilegeIdField === "" || amountField === "" || nameField === ""}
> >
Создать Создать
</Button> </Button>

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

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

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

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