Merge branch 'dev' into 'main'
Добавил jest для тестирования, использовал See merge request frontend/admin!5
This commit is contained in:
commit
e902e4b8fd
38015
package-lock.json
generated
Normal file
38015
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,10 +22,13 @@
|
|||||||
"@types/react": "^18.0.18",
|
"@types/react": "^18.0.18",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"axios": "^1.3.4",
|
||||||
"craco": "^0.0.3",
|
"craco": "^0.0.3",
|
||||||
"dayjs": "^1.11.5",
|
"dayjs": "^1.11.5",
|
||||||
|
"formik": "^2.2.9",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"nanoid": "^4.0.1",
|
"nanoid": "^4.0.1",
|
||||||
|
"notistack": "^3.0.1",
|
||||||
"numeral": "^2.0.6",
|
"numeral": "^2.0.6",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
44
src/__tests__/menu-link.test.js
Normal file
44
src/__tests__/menu-link.test.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
const url = "http://localhost:3000/users";
|
||||||
|
const urlMass = ['/users','/tariffs','/discounts','/promocode','/support', '/entities'];
|
||||||
|
|
||||||
|
jest.setTimeout(1000 * 60 * 5);
|
||||||
|
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
describe('Тест', (() => {
|
||||||
|
beforeAll(async()=>{
|
||||||
|
browser = puppeteer.launch({headless:true});
|
||||||
|
page = browser.newPage();
|
||||||
|
|
||||||
|
page.goto(url);
|
||||||
|
// Set screen size
|
||||||
|
page.setViewport({width: 1080, height: 1024});
|
||||||
|
|
||||||
|
})
|
||||||
|
afterAll(() => browser.quit());
|
||||||
|
test('Тест меню',async () => {
|
||||||
|
|
||||||
|
// Ждем загрузки менюшек
|
||||||
|
page.waitForSelector('.menu')
|
||||||
|
|
||||||
|
// Берем все ссылки с кнопок, у которых есть класс menu и вставляем в массив
|
||||||
|
let menuLink = page.evaluate(()=>{
|
||||||
|
let menuArray = document.querySelectorAll('.menu')
|
||||||
|
let Urls = Object.values(menuArray).map(
|
||||||
|
menuItem => (
|
||||||
|
menuItem.href.slice(menuItem.href.lastIndexOf('/'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return Urls
|
||||||
|
})
|
||||||
|
// Проверяем, какие ссылки есть в нашем массиве, а каких нет
|
||||||
|
for (let i = 0; i < menuLink.length; i++) {
|
||||||
|
expect(urlMass.find((elem)=>elem===menuLink[i])).toBe(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
@ -1,38 +1,61 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import CssBaseline from "@mui/material/CssBaseline";
|
||||||
|
import { SnackbarProvider } from 'notistack';
|
||||||
|
import { ThemeProvider } from '@mui/material/styles';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router-dom";
|
||||||
import Authorization from "./pages/Authorization";
|
import theme from "./theme";
|
||||||
import Sections from "./pages/Sections";
|
import PublicRoute from "@kitUI/publicRoute";
|
||||||
import LoggedIn from "./pages/dashboard";
|
import PrivateRoute from "@kitUI/privateRoute";
|
||||||
import Error404 from "./pages/Error404";
|
import Signin from "@pages/Authorization/signin";
|
||||||
|
import Signup from "@pages/Authorization/signup";
|
||||||
|
import Restore from "@pages/Authorization/restore";
|
||||||
|
import Sections from "@pages/Sections";
|
||||||
|
import Dashboard from "@pages/dashboard";
|
||||||
|
import Error404 from "@pages/Error404";
|
||||||
|
import Users from "@pages/dashboard/Content/Users";
|
||||||
|
import Entities from "@pages/dashboard/Content/Entities";
|
||||||
|
import Tariffs from "@pages/dashboard/Content/Tariffs";
|
||||||
|
import DiscountManagement from "@pages/dashboard/Content/DiscountManagement";
|
||||||
|
import PromocodeManagement from "@pages/dashboard/Content/PromocodeManagement";
|
||||||
|
import Support from "@pages/dashboard/Content/Support";
|
||||||
|
|
||||||
|
const componentsArray = [
|
||||||
|
["/users", <Users />],
|
||||||
|
["/entities",<Entities />],
|
||||||
|
["/tariffs", <Tariffs />],
|
||||||
|
["/discounts", <DiscountManagement />],
|
||||||
|
["/promocode", <PromocodeManagement />],
|
||||||
|
["/support", <Support />]
|
||||||
|
]
|
||||||
|
|
||||||
const container = document.getElementById('root');
|
const container = document.getElementById('root');
|
||||||
const root = createRoot(container!);
|
const root = createRoot(container!);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<CssBaseline />
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<SnackbarProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={ <Authorization /> } />
|
<Route path="/" element={<PublicRoute><Signin /></PublicRoute> } />
|
||||||
<Route path="/dispatch" element={ <Sections /> } />
|
<Route path="/signin" element={ <PublicRoute><Signin /></PublicRoute> } />
|
||||||
|
<Route path="/signup" element={ <PublicRoute><Signup /></PublicRoute> } />
|
||||||
|
<Route path="/restore" element={ <PublicRoute><Restore /></PublicRoute> } />
|
||||||
|
<Route path="/dispatch" element={ <PublicRoute><Sections /></PublicRoute> } />
|
||||||
|
<Route element={<PrivateRoute><Dashboard/></PrivateRoute>}>
|
||||||
|
{componentsArray.map((e:any, i) => (
|
||||||
|
<Route key={i} path={e[0]} element={e[1]} />
|
||||||
|
))}
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path="/users" element={ <LoggedIn section={1} /> } />
|
|
||||||
<Route path="/entities" element={ <LoggedIn section={2} /> } />
|
|
||||||
<Route path="/tariffs" element={ <LoggedIn section={3} /> } />
|
|
||||||
<Route path="/discounts" element={ <LoggedIn section={4} /> } />
|
|
||||||
<Route path="/promocode" element={ <LoggedIn section={5} /> } />
|
|
||||||
|
|
||||||
|
|
||||||
<Route path="/support" element={ <LoggedIn section={8} /> } />
|
|
||||||
|
|
||||||
<Route path="/modalAdmin" element={ <LoggedIn section={-1} /> } />
|
|
||||||
<Route path="/modalUser" element={ <LoggedIn section={-1} /> } />
|
|
||||||
<Route path="/modalEntities" element={ <LoggedIn section={-1} /> } />
|
|
||||||
<Route
|
<Route
|
||||||
path="*"
|
path="*"
|
||||||
element={ <Error404 /> }
|
element={ <Error404 /> }
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
</SnackbarProvider>
|
||||||
|
</ThemeProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
@ -1,5 +1,5 @@
|
|||||||
import theme from "@theme";
|
import theme from "@theme";
|
||||||
import { Button, Paper, Box, Typography, TableHead, TableRow, TableCell, TableBody, Table, Tooltip, Alert } from "@mui/material";
|
import { Button, Paper, Box, Typography, TableHead, TableRow, TableCell, TableBody, Table, Tooltip, Alert, Checkbox, FormControlLabel } from "@mui/material";
|
||||||
import Input from "@kitUI/input";
|
import Input from "@kitUI/input";
|
||||||
import { useCartStore } from "@root/stores/cart";
|
import { useCartStore } from "@root/stores/cart";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@ -22,7 +22,7 @@ export default function Cart({ selectedTariffs }: Props) {
|
|||||||
const setCartTotal = useCartStore(store => store.setCartTotal);
|
const setCartTotal = useCartStore(store => store.setCartTotal);
|
||||||
const [couponField, setCouponField] = useState<string>("");
|
const [couponField, setCouponField] = useState<string>("");
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
// const [coupon, setCoupon] = useState<string | undefined>();
|
const [isNonCommercial, setIsNonCommercial] = useState<boolean>(false);
|
||||||
|
|
||||||
const cartRows = cartTotal?.items.map(cartItemTotal => {
|
const cartRows = cartTotal?.items.map(cartItemTotal => {
|
||||||
const service = cartItemTotal.tariff.privilege.serviceKey;
|
const service = cartItemTotal.tariff.privilege.serviceKey;
|
||||||
@ -63,8 +63,8 @@ export default function Cart({ selectedTariffs }: Props) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const cartDiscounts = cartTotal?.envolvedCartDiscounts
|
const cartDiscounts = cartTotal?.envolvedCartDiscounts;
|
||||||
const cartDiscountsResultFactor = cartDiscounts && cartDiscounts.reduce((acc, discount) => acc * findDiscountFactor(discount), 1);
|
const cartDiscountsResultFactor = cartDiscounts && cartDiscounts?.length > 1 && cartDiscounts.reduce((acc, discount) => acc * findDiscountFactor(discount), 1);
|
||||||
|
|
||||||
const envolvedCartDiscountsElement = cartDiscounts && (
|
const envolvedCartDiscountsElement = cartDiscounts && (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
@ -89,7 +89,7 @@ export default function Cart({ selectedTariffs }: Props) {
|
|||||||
function handleCalcCartClick() {
|
function handleCalcCartClick() {
|
||||||
const cartTariffs = tariffs.filter(tariff => selectedTariffs.includes(tariff.id));
|
const cartTariffs = tariffs.filter(tariff => selectedTariffs.includes(tariff.id));
|
||||||
const cartItems = cartTariffs.map(tariff => createCartItem(tariff));
|
const cartItems = cartTariffs.map(tariff => createCartItem(tariff));
|
||||||
const cartData = calcCartData(testUser, cartItems, discounts, couponField);
|
const cartData = calcCartData(testUser, cartItems, discounts, isNonCommercial, couponField);
|
||||||
|
|
||||||
if (cartData instanceof Error) {
|
if (cartData instanceof Error) {
|
||||||
setErrorMessage(cartData.message);
|
setErrorMessage(cartData.message);
|
||||||
@ -118,8 +118,29 @@ export default function Cart({ selectedTariffs }: Props) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Paper
|
<Paper
|
||||||
variant="bar"
|
variant="bar"
|
||||||
sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: "20px",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
checked={isNonCommercial}
|
||||||
|
onChange={(e, checked) => setIsNonCommercial(checked)}
|
||||||
|
control={<Checkbox
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.secondary.main,
|
||||||
|
"&.Mui-checked": {
|
||||||
|
color: theme.palette.secondary.main,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
label="НКО"
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.secondary.main,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: "1px solid white",
|
border: "1px solid white",
|
||||||
@ -145,12 +166,14 @@ export default function Cart({ selectedTariffs }: Props) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* <Button
|
|
||||||
sx={{ maxWidth: "140px" }}
|
|
||||||
onClick={() => setCoupon(couponField)}
|
|
||||||
>применить промокод</Button> */}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={handleCalcCartClick}>рассчитать</Button>
|
{cartTotal?.couponState && (
|
||||||
|
cartTotal.couponState === "applied" ?
|
||||||
|
<Alert severity="success">Купон применен!</Alert>
|
||||||
|
:
|
||||||
|
<Alert severity="error">Подходящий купон не найден!</Alert>
|
||||||
|
)}
|
||||||
|
<Button onClick={handleCalcCartClick} sx={{ ml: "auto" }}>рассчитать</Button>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{cartTotal?.items && cartTotal.items.length > 0 &&
|
{cartTotal?.items && cartTotal.items.length > 0 &&
|
||||||
|
@ -188,7 +188,7 @@ describe("cart tests", () => {
|
|||||||
it("юзер использовал промокод id33. он заменяет скидку на p6 собой. в один момент времени может быть активирован только 1 промокод, т.е. после активации следующего, предыдущий заменяется. но в промокоде может быть несколько скидок. промокоды имеют скидки только на привелеги", () => {
|
it("юзер использовал промокод id33. он заменяет скидку на p6 собой. в один момент времени может быть активирован только 1 промокод, т.е. после активации следующего, предыдущий заменяется. но в промокоде может быть несколько скидок. промокоды имеют скидки только на привелеги", () => {
|
||||||
const testCase = prepareTestCase(exampleCartValues.testCases[9]);
|
const testCase = prepareTestCase(exampleCartValues.testCases[9]);
|
||||||
|
|
||||||
const cartTotal = calcCartData(testCase.user, testCase.cartItems, discounts, "ABCD") as CartTotal;
|
const cartTotal = calcCartData(testCase.user, testCase.cartItems, discounts, false, "ABCD") as CartTotal;
|
||||||
const allEnvolvedDiscounts: string[] = [...cartTotal.envolvedCartDiscounts.map(discount => discount._id)];
|
const allEnvolvedDiscounts: string[] = [...cartTotal.envolvedCartDiscounts.map(discount => discount._id)];
|
||||||
cartTotal.items.forEach(cartItem => {
|
cartTotal.items.forEach(cartItem => {
|
||||||
allEnvolvedDiscounts.push(...cartItem.envolvedDiscounts.map(discount => discount._id));
|
allEnvolvedDiscounts.push(...cartItem.envolvedDiscounts.map(discount => discount._id));
|
||||||
@ -204,7 +204,7 @@ describe("cart tests", () => {
|
|||||||
it("юзер подтвердил свой статус НКО, поэтому, не смотря на то что он достиг по лояльности уровня скидки id2, она не применилась, а применилась id32", () => {
|
it("юзер подтвердил свой статус НКО, поэтому, не смотря на то что он достиг по лояльности уровня скидки id2, она не применилась, а применилась id32", () => {
|
||||||
const testCase = prepareTestCase(exampleCartValues.testCases[10]);
|
const testCase = prepareTestCase(exampleCartValues.testCases[10]);
|
||||||
|
|
||||||
const cartTotal = calcCartData(testCase.user, testCase.cartItems, discounts) as CartTotal;
|
const cartTotal = calcCartData(testCase.user, testCase.cartItems, discounts, true) as CartTotal;
|
||||||
const allEnvolvedDiscounts: string[] = [...cartTotal.envolvedCartDiscounts.map(discount => discount._id)];
|
const allEnvolvedDiscounts: string[] = [...cartTotal.envolvedCartDiscounts.map(discount => discount._id)];
|
||||||
cartTotal.items.forEach(cartItem => {
|
cartTotal.items.forEach(cartItem => {
|
||||||
allEnvolvedDiscounts.push(...cartItem.envolvedDiscounts.map(discount => discount._id));
|
allEnvolvedDiscounts.push(...cartItem.envolvedDiscounts.map(discount => discount._id));
|
||||||
|
@ -7,6 +7,7 @@ export function calcCartData(
|
|||||||
user: User,
|
user: User,
|
||||||
cartItems: CartItem[],
|
cartItems: CartItem[],
|
||||||
discounts: AnyDiscount[],
|
discounts: AnyDiscount[],
|
||||||
|
isNonCommercial: boolean = false,
|
||||||
coupon?: string,
|
coupon?: string,
|
||||||
): CartTotal | Error | null {
|
): CartTotal | Error | null {
|
||||||
let isIncompatibleTariffs = false;
|
let isIncompatibleTariffs = false;
|
||||||
@ -44,11 +45,12 @@ export function calcCartData(
|
|||||||
dwarfener: null,
|
dwarfener: null,
|
||||||
},
|
},
|
||||||
envolvedCartDiscounts: [],
|
envolvedCartDiscounts: [],
|
||||||
|
couponState: coupon ? "not found" : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// layer 0
|
// layer 0
|
||||||
for (const discount of discounts) {
|
for (const discount of discounts) {
|
||||||
if (discount.conditionType !== "userType" || discount.condition.userType !== user.Type) continue;
|
if (discount.conditionType !== "userType" || !isNonCommercial) continue;
|
||||||
|
|
||||||
cartItems.forEach(cartItem => {
|
cartItems.forEach(cartItem => {
|
||||||
cartTotal.items.push({
|
cartTotal.items.push({
|
||||||
@ -86,6 +88,7 @@ export function calcCartData(
|
|||||||
|
|
||||||
cartItemTotal.totalPrice *= product.factor;
|
cartItemTotal.totalPrice *= product.factor;
|
||||||
cartItemTotal.envolvedDiscounts.push(couponDiscount);
|
cartItemTotal.envolvedDiscounts.push(couponDiscount);
|
||||||
|
cartTotal.couponState = "applied";
|
||||||
privilegesAffectedByCoupon.push(product.privilegeId);
|
privilegesAffectedByCoupon.push(product.privilegeId);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ export function calcCartData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// layer 4
|
// layer 4
|
||||||
const totalPurchasesAmountDiscount = findMaxTotalPurchasesAmountDiscount(discounts, user);
|
const totalPurchasesAmountDiscount = findMaxTotalPurchasesAmountDiscount(discounts, user.PurchasesAmount);
|
||||||
if (totalPurchasesAmountDiscount) {
|
if (totalPurchasesAmountDiscount) {
|
||||||
cartTotal.totalPrice *= totalPurchasesAmountDiscount.factor;
|
cartTotal.totalPrice *= totalPurchasesAmountDiscount.factor;
|
||||||
cartTotal.envolvedCartDiscounts.push(totalPurchasesAmountDiscount);
|
cartTotal.envolvedCartDiscounts.push(totalPurchasesAmountDiscount);
|
||||||
@ -164,9 +167,9 @@ function findMaxCartPurchasesAmountDiscount(discounts: AnyDiscount[], cartTotal:
|
|||||||
return maxValueDiscount;
|
return maxValueDiscount;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findMaxTotalPurchasesAmountDiscount(discounts: AnyDiscount[], user: User): PurchasesAmountDiscount | null {
|
function findMaxTotalPurchasesAmountDiscount(discounts: AnyDiscount[], purchasesAmount: number): PurchasesAmountDiscount | null {
|
||||||
const applicableDiscounts = discounts.filter((discount): discount is PurchasesAmountDiscount => {
|
const applicableDiscounts = discounts.filter((discount): discount is PurchasesAmountDiscount => {
|
||||||
return discount.conditionType === "purchasesAmount" && user.PurchasesAmount >= discount.condition.purchasesAmount;
|
return discount.conditionType === "purchasesAmount" && purchasesAmount >= discount.condition.purchasesAmount;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!applicableDiscounts.length) return null;
|
if (!applicableDiscounts.length) return null;
|
||||||
|
26
src/kitUI/cleverButton.tsx
Normal file
26
src/kitUI/cleverButton.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { Button, Skeleton} from "@mui/material";
|
||||||
|
const BeautifulButton = styled(Button)(({ theme }) => ({
|
||||||
|
width: "250px",
|
||||||
|
margin: "15px auto",
|
||||||
|
padding: "20px 30px",
|
||||||
|
fontSize: 18
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isReady: boolean
|
||||||
|
text:string
|
||||||
|
type?: "button" | "reset" | "submit"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
isReady = true,
|
||||||
|
text,
|
||||||
|
type = "button"
|
||||||
|
}:Props) => {
|
||||||
|
|
||||||
|
if (isReady) {
|
||||||
|
return <BeautifulButton type={type}>{text}</BeautifulButton>
|
||||||
|
}
|
||||||
|
return <Skeleton>{text}</Skeleton>
|
||||||
|
}
|
65
src/kitUI/makeRequest.ts
Normal file
65
src/kitUI/makeRequest.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
interface MakeRequest {
|
||||||
|
method?: string
|
||||||
|
url: string
|
||||||
|
body?: unknown
|
||||||
|
useToken?: boolean
|
||||||
|
contentType?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (props: MakeRequest) => {
|
||||||
|
return (
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
await makeRequest(props)
|
||||||
|
.then(r => resolve(r))
|
||||||
|
.catch(r => reject(r))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRequest({
|
||||||
|
method = "post",
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
useToken = true,
|
||||||
|
contentType = false
|
||||||
|
}: MakeRequest) {
|
||||||
|
//В случае 401 рефреш должен попробовать вызваться 1 раз
|
||||||
|
let counterRefresh = true
|
||||||
|
let headers: any = {}
|
||||||
|
if (useToken) headers["Authorization"] = localStorage.getItem('AT')
|
||||||
|
if (contentType) headers["Content-Type"] = "application/json"
|
||||||
|
return axios({
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
headers: headers,
|
||||||
|
data: body
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.data && response.data.accessToken) {
|
||||||
|
localStorage.setItem('AT', response.data.accessToken)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.response.status == 401 && counterRefresh) {
|
||||||
|
refresh().then(response => {
|
||||||
|
if (response.data && response.data.accessToken) localStorage.setItem('AT', response.data.accessToken)
|
||||||
|
counterRefresh = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
return axios("https://admin.pena.digital/auth/refresh", {
|
||||||
|
headers: {
|
||||||
|
"Authorization": localStorage.getItem('AT'),
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
15
src/kitUI/outlinedInput.tsx
Normal file
15
src/kitUI/outlinedInput.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { styled } from "@mui/material/styles";
|
||||||
|
import { TextField} from "@mui/material";
|
||||||
|
export default styled(TextField)(({ theme }) => ({
|
||||||
|
color: theme.palette.grayLight.main,
|
||||||
|
"& .MuiInputLabel-root": {
|
||||||
|
color: theme.palette.grayLight.main,
|
||||||
|
},
|
||||||
|
"& .MuiFilledInput-root": {
|
||||||
|
border: theme.palette.grayLight.main+" 1px solid",
|
||||||
|
borderRadius: "0",
|
||||||
|
backgroundColor: theme.palette.hover.main,
|
||||||
|
color: theme.palette.grayLight.main,
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
12
src/kitUI/privateRoute.tsx
Normal file
12
src/kitUI/privateRoute.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { useLocation, Navigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
export default ({ children }: any) => {
|
||||||
|
const location = useLocation()
|
||||||
|
//Если пользователь авторизован, перенаправляем его на нужный путь. Иначе выкидываем в регистрацию
|
||||||
|
if (localStorage.getItem('AT')) {
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
return <Navigate to="/" state={{from: location}} />
|
||||||
|
|
||||||
|
}
|
12
src/kitUI/publicRoute.tsx
Normal file
12
src/kitUI/publicRoute.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { useLocation, Navigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
export default ({ children }: any) => {
|
||||||
|
const location = useLocation()
|
||||||
|
//Если пользователь авторизован, перенаправляем его в приложение. Иначе пускаем куда хотел
|
||||||
|
if (localStorage.getItem('AT')) {
|
||||||
|
return <Navigate to="/users" state={{from: location}} />
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
|
||||||
|
}
|
@ -140,4 +140,5 @@ export interface CartTotal {
|
|||||||
discountsByService: ServiceToDiscountMap;
|
discountsByService: ServiceToDiscountMap;
|
||||||
/** Учтенные скидки типов userType, cartPurchasesAmount, totalPurchasesAmount */
|
/** Учтенные скидки типов userType, cartPurchasesAmount, totalPurchasesAmount */
|
||||||
envolvedCartDiscounts: (UserTypeDiscount | CartPurchasesAmountDiscount | PurchasesAmountDiscount)[];
|
envolvedCartDiscounts: (UserTypeDiscount | CartPurchasesAmountDiscount | PurchasesAmountDiscount)[];
|
||||||
|
couponState: "applied" | "not found" | null;
|
||||||
}
|
}
|
@ -1,252 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { Box, Typography, TextField, Checkbox, Button } from "@mui/material";
|
|
||||||
import { ThemeProvider } from "@mui/material";
|
|
||||||
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
|
||||||
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
|
||||||
import theme from "../../theme";
|
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
|
||||||
import Logo from "../Logo";
|
|
||||||
|
|
||||||
|
|
||||||
const Authorization: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<ThemeProvider theme={theme}>
|
|
||||||
<CssBaseline />
|
|
||||||
<Box sx={{
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
color: theme.palette.secondary.main,
|
|
||||||
height: "100%"
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
width: "100vw",
|
|
||||||
height: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center"
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
maxWidth: "370px",
|
|
||||||
height: "700px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: '0 10px'
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<Logo />
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
width: "100%"
|
|
||||||
}}>
|
|
||||||
<Typography variant="h5">
|
|
||||||
Добро пожаловать
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h6" style={{ textAlign: "left" }}>
|
|
||||||
Мы рады что вы выбрали нас!
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "135px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center"
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%",
|
|
||||||
height: "65px"
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
width: "50px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "left"
|
|
||||||
}}>
|
|
||||||
<EmailOutlinedIcon
|
|
||||||
sx={{ color: theme.palette.golden.main }}
|
|
||||||
fontSize="large" />
|
|
||||||
</Box>
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center"
|
|
||||||
}}>
|
|
||||||
<TextField
|
|
||||||
id = "standard-basic"
|
|
||||||
label = "Эл. почта"
|
|
||||||
variant = "filled"
|
|
||||||
color = "secondary"
|
|
||||||
sx = {{ }}
|
|
||||||
InputProps={{
|
|
||||||
style: {
|
|
||||||
backgroundColor: theme.palette.content.main,
|
|
||||||
color: theme.palette.secondary.main,
|
|
||||||
paddingRight: '30px'
|
|
||||||
} }}
|
|
||||||
InputLabelProps={{
|
|
||||||
style: {
|
|
||||||
color: theme.palette.secondary.main
|
|
||||||
} }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%",
|
|
||||||
height: "65px",
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
width: "50px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "left"
|
|
||||||
}}>
|
|
||||||
<LockOutlinedIcon
|
|
||||||
sx={{ color: theme.palette.golden.main }}
|
|
||||||
fontSize="large" />
|
|
||||||
</Box>
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center"
|
|
||||||
}}>
|
|
||||||
<TextField
|
|
||||||
id = "outlined-password-input"
|
|
||||||
label = "Пароль"
|
|
||||||
type = "password"
|
|
||||||
variant = "filled"
|
|
||||||
color = "secondary"
|
|
||||||
sx = {{ }}
|
|
||||||
InputProps={{
|
|
||||||
style: {
|
|
||||||
backgroundColor: theme.palette.content.main,
|
|
||||||
color: theme.palette.secondary.main,
|
|
||||||
paddingRight: '30px'
|
|
||||||
} }}
|
|
||||||
InputLabelProps={{
|
|
||||||
style: {
|
|
||||||
color: theme.palette.secondary.main
|
|
||||||
} }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "75px"
|
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%"
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
width: "50px",
|
|
||||||
height: "46px"
|
|
||||||
}}>
|
|
||||||
<Checkbox sx={{
|
|
||||||
color: theme.palette.secondary.main,
|
|
||||||
transform: "scale(1.5)",
|
|
||||||
'&.Mui-checked': {
|
|
||||||
color: theme.palette.secondary.main,
|
|
||||||
},
|
|
||||||
}} />
|
|
||||||
</Box>
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
fontWeight: "600"
|
|
||||||
}}>
|
|
||||||
Запомнить этот компьютер
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%"
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<Typography
|
|
||||||
variant = "h4"
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.golden.main,
|
|
||||||
cursor: "pointer",
|
|
||||||
"&:hover": {
|
|
||||||
color: theme.palette.goldenDark.main
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
Забыли пароль?
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant = "contained"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: theme.palette.content.main,
|
|
||||||
width: "100%",
|
|
||||||
padding: '21px 16px',
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.menu.main
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
ВОЙТИ
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
width: "100%",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
}}>
|
|
||||||
<Typography
|
|
||||||
variant = "h4">
|
|
||||||
У вас нет аккаунта?
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant = "h4"
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.golden.main,
|
|
||||||
borderBottom: `1px solid ${theme.palette.golden.main}`,
|
|
||||||
cursor: "pointer",
|
|
||||||
"&:hover": {
|
|
||||||
color: theme.palette.goldenDark.main
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
Зарегистрируйтесь
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</ThemeProvider>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default Authorization;
|
|
119
src/pages/Authorization/restore.tsx
Normal file
119
src/pages/Authorization/restore.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Formik, Field, Form } from 'formik';
|
||||||
|
import {useTheme} from "@mui/material/styles";
|
||||||
|
import { Link } from "react-router-dom"
|
||||||
|
import {Box, Typography} from "@mui/material";
|
||||||
|
import Logo from "@pages/Logo";
|
||||||
|
import CleverButton from "@kitUI/cleverButton"
|
||||||
|
import MakeRequest from "@kitUI/makeRequest";
|
||||||
|
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||||
|
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
|
||||||
|
import OutlinedInput from "@kitUI/outlinedInput";
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [restore, setRestore] = React.useState(true)
|
||||||
|
const [isReady, setIsReady] = React.useState(true)
|
||||||
|
if (restore) {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
mail: ""
|
||||||
|
}}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
setRestore(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" color={theme.palette.secondary.main}>Восстановление пароля</Typography>
|
||||||
|
<Logo/>
|
||||||
|
|
||||||
|
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||||
|
<EmailOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||||
|
<Field as={OutlinedInput} autoComplete="none" variant="filled" name="mail" label="Эл. почта"/>
|
||||||
|
</Box>
|
||||||
|
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||||
|
<Link to="/signin" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Я помню пароль</Typography></Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return(
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
code: ""
|
||||||
|
}}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" color={theme.palette.secondary.main}>Восстановление пароля</Typography>
|
||||||
|
<Logo/>
|
||||||
|
|
||||||
|
<Box sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||||
|
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||||
|
<Field as={OutlinedInput} name="code" variant="filled" label="Код из сообщения"/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||||
|
<Link to="/signin" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Я помню пароль</Typography></Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
137
src/pages/Authorization/signin.tsx
Normal file
137
src/pages/Authorization/signin.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { enqueueSnackbar } from 'notistack';
|
||||||
|
import {useTheme} from "@mui/material/styles"
|
||||||
|
import { Formik, Field, Form } from 'formik'
|
||||||
|
import { Link } from "react-router-dom"
|
||||||
|
import { Box, Checkbox, TextField, Typography, FormControlLabel} from "@mui/material"
|
||||||
|
import Logo from "@pages/Logo"
|
||||||
|
import CleverButton from "@kitUI/cleverButton"
|
||||||
|
import OutlinedInput from "@kitUI/outlinedInput"
|
||||||
|
import makeRequest from "@kitUI/makeRequest";
|
||||||
|
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined";
|
||||||
|
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"
|
||||||
|
|
||||||
|
interface Values {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(values: Values) {
|
||||||
|
const errors = {} as any;
|
||||||
|
if (!values.email) {
|
||||||
|
errors.email = "Required";
|
||||||
|
}
|
||||||
|
if (!values.password) {
|
||||||
|
errors.password = "Required";
|
||||||
|
} else if (!/^[\w-]{8,25}$/i.test(values.password)) {
|
||||||
|
errors.password = "Invalid password";
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
export default () => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [isReady, setIsReady] = React.useState(true)
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
email: "",
|
||||||
|
password: ""
|
||||||
|
}}
|
||||||
|
validate={validate}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
makeRequest({
|
||||||
|
url: "https://admin.pena.digital/auth/login",
|
||||||
|
body: {
|
||||||
|
"email": values.email,
|
||||||
|
"password": values.password
|
||||||
|
},
|
||||||
|
useToken: false
|
||||||
|
})
|
||||||
|
.then((e) => {
|
||||||
|
console.log(e)
|
||||||
|
navigate("/users")
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
enqueueSnackbar(e.message ? e.message : `Unknown error`)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<CleverButton type="submit" text="Войти" isReady={isReady}/>
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex"
|
||||||
|
}}>
|
||||||
|
<Typography color={theme.palette.secondary.main}>У вас нет аккаунта? </Typography>
|
||||||
|
<Link to="/signup" style={{textDecoration:"none"}}><Typography color={theme.palette.golden.main}>Зарегестрируйтесь</Typography></Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
}
|
118
src/pages/Authorization/signup.tsx
Normal file
118
src/pages/Authorization/signup.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { enqueueSnackbar } from 'notistack';
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useTheme } from "@mui/material/styles"
|
||||||
|
import { Formik, Field, Form } from "formik"
|
||||||
|
import { Link } from "react-router-dom"
|
||||||
|
import {Box, Typography} from "@mui/material"
|
||||||
|
import CleverButton from "@kitUI/cleverButton"
|
||||||
|
import OutlinedInput from "@kitUI/outlinedInput";
|
||||||
|
import makeRequest from "@kitUI/makeRequest";
|
||||||
|
import Logo from "@pages/Logo/index"
|
||||||
|
import EmailOutlinedIcon from "@mui/icons-material/EmailOutlined"
|
||||||
|
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"
|
||||||
|
interface Values {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
repeatPassword: string;
|
||||||
|
}
|
||||||
|
function validate(values: Values) {
|
||||||
|
const errors = {} as any;
|
||||||
|
if (!values.email) {
|
||||||
|
errors.login = "Required";
|
||||||
|
}
|
||||||
|
if (!values.password) {
|
||||||
|
errors.password = "Required";
|
||||||
|
} else if (!/^[\w-]{8,25}$/i.test(values.password)) {
|
||||||
|
errors.password = "Invalid password";
|
||||||
|
}
|
||||||
|
if (values.password !== values.repeatPassword) {
|
||||||
|
errors.repeatPassword = "Passwords do not match";
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
export default () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const theme = useTheme()
|
||||||
|
const [isReady, setIsReady] = React.useState(true)
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
repeatPassword: ""
|
||||||
|
}}
|
||||||
|
validate={validate}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
makeRequest({
|
||||||
|
url: "https://admin.pena.digital/auth/register",
|
||||||
|
body: {
|
||||||
|
"login": "login",
|
||||||
|
"email": values.email,
|
||||||
|
"password": values.repeatPassword,
|
||||||
|
"phoneNumber": "+89999999999"
|
||||||
|
},
|
||||||
|
useToken: false
|
||||||
|
})
|
||||||
|
.then((e) => {
|
||||||
|
navigate("/users")
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
enqueueSnackbar(e.response && e.response.data && e.response.data.message ? e.response.data.message : `Unknown error`)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" color={theme.palette.secondary.main}>Новый аккаунт</Typography>
|
||||||
|
<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 sx={{display:"flex", alignItems:"center",marginTop:"15px","> *": {marginRight:"10px"}}}>
|
||||||
|
<LockOutlinedIcon htmlColor={theme.palette.golden.main}/>
|
||||||
|
<Field as={OutlinedInput} type="password" name="repeatPassword" variant="filled" label="Повторите пароль"/>
|
||||||
|
</Box>
|
||||||
|
<CleverButton type="submit" text="Отправить" isReady={isReady}/>
|
||||||
|
<Link to="/signin" style={{textDecoration: "none"}} ><Typography color={theme.palette.golden.main}>У меня уже есть аккаунт</Typography></Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { Box } from "@mui/material";
|
|
||||||
import Users from "./Users";
|
|
||||||
import Entities from "./Entities";
|
|
||||||
import Tariffs from "./Tariffs";
|
|
||||||
import DiscountManagement from "./DiscountManagement";
|
|
||||||
import PromocodeManagement from "./PromocodeManagement";
|
|
||||||
|
|
||||||
|
|
||||||
import Support from "./Support";
|
|
||||||
import Error404 from "../../Error404";
|
|
||||||
|
|
||||||
|
|
||||||
export interface MWProps {
|
|
||||||
section: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const Content: React.FC<MWProps> = ({ section }) => {
|
|
||||||
const componentsArray = [
|
|
||||||
<Error404 />,
|
|
||||||
<Users />,
|
|
||||||
<Entities />,
|
|
||||||
<Tariffs />,
|
|
||||||
<DiscountManagement />,
|
|
||||||
<PromocodeManagement />,
|
|
||||||
<Error404 />,
|
|
||||||
<Error404 />,
|
|
||||||
<Support />
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<Box sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
overflow: "auto",
|
|
||||||
overflowY: "auto",
|
|
||||||
padding: "160px 5px"
|
|
||||||
}}>
|
|
||||||
|
|
||||||
{ componentsArray[ section ] }
|
|
||||||
</Box>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default Content;
|
|
@ -1,8 +1,9 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Box, Typography } from "@mui/material";
|
import {Box, IconButton, Typography} from "@mui/material";
|
||||||
import theme from "../../../theme";
|
import theme from "../../../theme";
|
||||||
import ExitToAppOutlinedIcon from '@mui/icons-material/ExitToAppOutlined';
|
import ExitToAppOutlinedIcon from '@mui/icons-material/ExitToAppOutlined';
|
||||||
import Logo from "../../Logo";
|
import Logo from "../../Logo";
|
||||||
|
import makeRequest from "@kitUI/makeRequest";
|
||||||
|
|
||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
@ -43,7 +44,15 @@ const Header: React.FC = () => {
|
|||||||
Добро пожаловать, Администратор сервиса
|
Добро пожаловать, Администратор сервиса
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{
|
<IconButton
|
||||||
|
onClick={()=>{
|
||||||
|
makeRequest({
|
||||||
|
url: "https://admin.pena.digital/auth/logout",
|
||||||
|
contentType: true
|
||||||
|
})
|
||||||
|
.then(()=>localStorage.setItem('AT', ""))
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
@ -55,7 +64,7 @@ const Header: React.FC = () => {
|
|||||||
color: theme.palette.golden.main,
|
color: theme.palette.golden.main,
|
||||||
transform: "scale(1.3)"
|
transform: "scale(1.3)"
|
||||||
}} />
|
}} />
|
||||||
</Box>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
@ -101,15 +101,15 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open'
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const links: {path: string; element: JSX.Element; title: string} [] =[
|
const links: {path: string; element: JSX.Element; title: string, className: string} [] =[
|
||||||
{path: '/users', element: <PersonOutlineOutlinedIcon/>, title: 'Информация о проекте'},
|
{path: '/users', element: <PersonOutlineOutlinedIcon/>, title: 'Информация о проекте', className:'menu'},
|
||||||
{path: '/entities', element: <SettingsOutlinedIcon/>, title: 'Юридические лица'},
|
{path: '/entities', element: <SettingsOutlinedIcon/>, title: 'Юридические лица', className:'menu'},
|
||||||
{path: '/tariffs', element: <BathtubOutlinedIcon/>, title: 'Шаблонизатор документов'},
|
{path: '/tariffs', element: <BathtubOutlinedIcon/>, title: 'Шаблонизатор документов', className:'menu'},
|
||||||
{path: '/discounts', element: <AddPhotoAlternateOutlinedIcon/>, title: 'Скидки'},
|
{path: '/discounts', element: <AddPhotoAlternateOutlinedIcon/>, title: 'Скидки', className:'menu'},
|
||||||
{path: '/promocode', element: <NaturePeopleOutlinedIcon/>, title: 'Промокод'},
|
{path: '/promocode', element: <NaturePeopleOutlinedIcon/>, title: 'Промокод', className:'menu'},
|
||||||
{path: '/kkk', element: <SettingsIcon/>, title: 'Настройки'},
|
{path: '/kkk', element: <SettingsIcon/>, title: 'Настройки', className:'menu'},
|
||||||
{path: '/jjj', element: <CameraIcon/>, title: 'Камера' },
|
{path: '/jjj', element: <CameraIcon/>, title: 'Камера', className:'menu'},
|
||||||
{path: '/support', element: <HeadsetMicOutlinedIcon/>, title: 'Служба поддержки'},
|
{path: '/support', element: <HeadsetMicOutlinedIcon/>, title: 'Служба поддержки', className:'menu'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ const Navigation = (props:any) => {
|
|||||||
>
|
>
|
||||||
{links.map((e, i) => (
|
{links.map((e, i) => (
|
||||||
<ListItem key={i} disablePadding sx={{ display: 'block' }}>
|
<ListItem key={i} disablePadding sx={{ display: 'block' }}>
|
||||||
<Link to={e.path} style={{textDecoration: 'none'}}>
|
<Link to={e.path} style={{textDecoration: 'none'}} className={e.className}>
|
||||||
<ListItemButton onClick={props.SladeMobileHC}
|
<ListItemButton onClick={props.SladeMobileHC}
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: 48,
|
minHeight: 48,
|
||||||
|
@ -1,31 +1,26 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import {Outlet} from 'react-router-dom'
|
||||||
|
import {useTheme} from '@mui/material/styles';
|
||||||
import {Box} from "@mui/material";
|
import {Box} from "@mui/material";
|
||||||
import {ThemeProvider} from "@mui/material";
|
import {ThemeProvider} from "@mui/material";
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
import Menu from "./Menu";
|
import Menu from "./Menu";
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
import Content from "./Content";
|
|
||||||
import ModalAdmin from "./ModalAdmin";
|
import ModalAdmin from "./ModalAdmin";
|
||||||
import ModalUser from "./ModalUser";
|
import ModalUser from "./ModalUser";
|
||||||
import ModalEntities from "./ModalEntities";
|
import ModalEntities from "./ModalEntities";
|
||||||
import theme from "../../theme";
|
|
||||||
import {useMatch} from "react-router-dom";
|
import {useMatch} from "react-router-dom";
|
||||||
|
|
||||||
|
export default () => {
|
||||||
export interface MWProps {
|
const theme = useTheme()
|
||||||
section: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoggedIn: React.FC<MWProps> = ({ section }) => {
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ThemeProvider theme={theme}>
|
|
||||||
<CssBaseline />
|
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
color: theme.palette.secondary.main,
|
color: theme.palette.secondary.main,
|
||||||
height: "100%"
|
height: "100%"
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
backgroundColor: theme.palette.content.main,
|
backgroundColor: theme.palette.content.main,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -41,8 +36,18 @@ const LoggedIn: React.FC<MWProps> = ({ section }) => {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}>
|
}}>
|
||||||
{/*<Header />*/}
|
<Box sx={{
|
||||||
<Content section={ section } />
|
width: "100%",
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
overflow: "auto",
|
||||||
|
overflowY: "auto",
|
||||||
|
padding: "160px 5px"
|
||||||
|
}}>
|
||||||
|
<Outlet/>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -50,11 +55,6 @@ const LoggedIn: React.FC<MWProps> = ({ section }) => {
|
|||||||
<ModalAdmin open={useMatch('/modalAdmin') !== null}/>
|
<ModalAdmin open={useMatch('/modalAdmin') !== null}/>
|
||||||
<ModalUser open={useMatch('/modalUser') !== null}/>
|
<ModalUser open={useMatch('/modalUser') !== null}/>
|
||||||
<ModalEntities open={useMatch('/modalEntities') !== null}/>
|
<ModalEntities open={useMatch('/modalEntities') !== null}/>
|
||||||
|
|
||||||
</ThemeProvider>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default LoggedIn;
|
|
||||||
|
24
src/theme.ts
24
src/theme.ts
@ -28,6 +28,9 @@ declare module '@mui/material/styles' {
|
|||||||
content: {
|
content: {
|
||||||
main: string;
|
main: string;
|
||||||
},
|
},
|
||||||
|
hover: {
|
||||||
|
main: string;
|
||||||
|
},
|
||||||
grayLight: {
|
grayLight: {
|
||||||
main: string;
|
main: string;
|
||||||
},
|
},
|
||||||
@ -83,7 +86,7 @@ declare module '@mui/material/styles' {
|
|||||||
const fontFamily: string = "GilroyRegular";
|
const fontFamily: string = "GilroyRegular";
|
||||||
const fontWeight: string = "600";
|
const fontWeight: string = "600";
|
||||||
|
|
||||||
const options1 = {
|
const paletteColor = {
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: "#111217"
|
main: "#111217"
|
||||||
@ -123,7 +126,7 @@ const options1 = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const options2 = {
|
const theme = {
|
||||||
typography: {
|
typography: {
|
||||||
body1: {
|
body1: {
|
||||||
fontFamily: fontFamily
|
fontFamily: fontFamily
|
||||||
@ -169,12 +172,12 @@ const options2 = {
|
|||||||
MuiButton: {
|
MuiButton: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
color: options1.palette.secondary.main,
|
color: paletteColor.palette.secondary.main,
|
||||||
backgroundColor: options1.palette.menu.main,
|
backgroundColor: paletteColor.palette.menu.main,
|
||||||
padding: "12px",
|
padding: "12px",
|
||||||
fontSize: "13px",
|
fontSize: "13px",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: options1.palette.hover.main,
|
backgroundColor: paletteColor.palette.hover.main,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -184,11 +187,11 @@ const options2 = {
|
|||||||
variant: 'enter'
|
variant: 'enter'
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
color: options1.palette.secondary.main,
|
color: paletteColor.palette.secondary.main,
|
||||||
backgroundColor: options1.palette.content.main,
|
backgroundColor: paletteColor.palette.content.main,
|
||||||
padding: '12px 48px',
|
padding: '12px 48px',
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: options1.palette.hover.main,
|
backgroundColor: paletteColor.palette.hover.main,
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -202,7 +205,7 @@ const options2 = {
|
|||||||
variant: "bar"
|
variant: "bar"
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: options1.palette.grayMedium.main,
|
backgroundColor: paletteColor.palette.grayMedium.main,
|
||||||
padding: "15px",
|
padding: "15px",
|
||||||
width: "100%"
|
width: "100%"
|
||||||
}
|
}
|
||||||
@ -213,5 +216,4 @@ const options2 = {
|
|||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
const theme = createTheme(deepmerge(options1, options2));
|
export default createTheme(deepmerge(paletteColor, theme));
|
||||||
export default theme;
|
|
@ -5,7 +5,8 @@
|
|||||||
"@theme": ["./theme.ts"],
|
"@theme": ["./theme.ts"],
|
||||||
"@root/*": ["./*"],
|
"@root/*": ["./*"],
|
||||||
"@kitUI/*": ["./kitUI/*"],
|
"@kitUI/*": ["./kitUI/*"],
|
||||||
"@stores/*": ["./stores/*"]
|
"@stores/*": ["./stores/*"],
|
||||||
|
"@pages/*": ["./pages/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user