tasks 16.12.2021 - 27.12.2021, auth, settings, fines

This commit is contained in:
merelendor 2021-12-27 21:21:53 +01:00
parent 57681ba377
commit 52c567df49
37 changed files with 5078 additions and 266 deletions

View File

@ -76,7 +76,7 @@ export const sendLoginFormPhone = ({ phone }) =>
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/auth/phone/check`, { phone })
.then((response) =>
{
console.log("sendTermsForm RESPONSE");
console.log("sendLoginFormPhone RESPONSE");
console.log(response.data);
if(response.data)
@ -108,7 +108,7 @@ export const sendSmsCode = ({ dispatch, phone, code }) =>
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/auth/phone/code`, { phone, code })
.then((response) =>
{
console.log("checkSmsCode RESPONSE");
console.log("sendSmsCode RESPONSE");
console.log(response.data);
if(response.data.status === "success")

View File

@ -294,4 +294,28 @@ export const getContractRules = ({ dispatch, date, }) =>
reject();
});
});
}
export const getContractMaterials = ({ dispatch, }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/contract/materials`, {
},
{
withCredentials: true,
})
.then(async (response) =>
{
console.log("getContractMaterials", "response.data", response.data);
dispatch({ type: actionTypes.CONTRACT_MATERIALS, data: { materials: response.data.materials } });
resolve();
})
.catch((error) =>
{
console.error(error);
reject();
});
});
}

View File

@ -70,6 +70,54 @@ export const getPrintFile = ({ contract, num, type, date, filename }) =>
});
}
export const getFineBeforeAccrualFile = ({ contract, num, filename }) =>
{
console.log("getFile");
return new Promise((resolve, reject) =>
{
axios.get(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/file/fine-before-accrual`, {
params: { contract, num, },
responseType: 'blob',
})
.then((response) =>
{
fileDownload(response.data, filename);
resolve();
})
.catch((error) =>
{
console.log("error");
console.error(error);
reject();
});
});
}
export const getFineAfterAccrualFile = ({ contract, num, filename }) =>
{
console.log("getFile");
return new Promise((resolve, reject) =>
{
axios.get(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/file/fine-after-accrual`, {
params: { contract, num, },
responseType: 'blob',
})
.then((response) =>
{
fileDownload(response.data, filename);
resolve();
})
.catch((error) =>
{
console.log("error");
console.error(error);
reject();
});
});
}
export const getReconciliationFile = ({ contract, date_from, date_to, filename }) =>
{
console.log("getFile");

View File

@ -5,4 +5,5 @@ export * from './calendarActions';
export * from './companyActions';
export * from './fileActions';
export * from './navigationActions';
export * from './formsActions';
export * from './formsActions';
export * from './settingsActions';

141
actions/recoveryActions.js Normal file
View File

@ -0,0 +1,141 @@
import axios from 'axios';
import { Cookies } from 'react-cookie';
import Router from 'next/router';
import moment from 'moment';
import * as actionTypes from '../constants/actionTypes';
import * as currentState from '../reducers/initialState';
if(process.browser)
{
FormData.prototype.appendObject = function(obj, namespace)
{
let keyName;
for (var key in obj)
{
if (obj.hasOwnProperty(key))
{
keyName = [namespace, '[', key, ']'].join('');
this.append(keyName, obj[key]);
}
}
};
}
export const sendEmailCheck = ({ email }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/recovery/check`, { email })
.then((response) =>
{
console.log("sendEmailCheck RESPONSE");
console.log(response.data);
if(response.data)
{
console.log("DATA? ");
resolve();
}
else
{
console.log("DATA ! ");
reject();
}
})
.catch((error) =>
{
console.error("error");
console.error(error);
reject();
});
});
}
export const sendEmailCode = ({ email, code }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/recovery/code`, { email, code })
.then((response) =>
{
console.log("checkSmsCode RESPONSE");
console.log(response.data);
if(response.data.status === "success")
{
resolve();
}
else
{
reject();
}
})
.catch((error) =>
{
console.error("error");
console.error(error);
reject();
});
});
}
export const sendPassword = ({ email, code, password, password_repeat }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/recovery/password`, { email, code, password, password_repeat })
.then((response) =>
{
console.log("sendPassword RESPONSE");
console.log(response.data);
if(response.data.status === "success")
{
resolve();
}
else
{
reject();
}
})
.catch((error) =>
{
console.error("error");
console.error(error);
reject();
});
});
}
export const sendNewPassword = ({ password, new_password, new_password_repeat }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/change/password`, { password, new_password, new_password_repeat })
.then((response) =>
{
console.log("sendNewPassword RESPONSE");
console.log(response.data);
if(response.data.status === "success")
{
resolve();
}
else
{
reject();
}
})
.catch((error) =>
{
console.error("error");
console.error(error);
reject();
});
});
}

119
actions/settingsActions.js Normal file
View File

@ -0,0 +1,119 @@
import axios from 'axios';
import { Cookies } from 'react-cookie';
import Router from 'next/router';
import moment from 'moment';
import * as actionTypes from '../constants/actionTypes';
import * as currentState from '../reducers/initialState';
if(process.browser)
{
FormData.prototype.appendObject = function(obj, namespace)
{
let keyName;
for (var key in obj)
{
if (obj.hasOwnProperty(key))
{
keyName = [namespace, '[', key, ']'].join('');
this.append(keyName, obj[key]);
}
}
};
}
export const sendPhoneChangeNumber = ({ email, phone }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/change/phone/send`, { email, phone })
.then((response) =>
{
console.log("\n", "sendLoginFormPhone", "RESPONSE");
console.log(response.data);
if(response.data)
{
console.log("DATA? ");
resolve();
}
else
{
console.log("DATA ! ");
reject();
}
})
.catch((error) =>
{
console.log("DATA !!! ");
console.log("error");
console.error(error);
reject();
});
});
}
export const sendPhoneChangeNumberSmsCode = ({ phone, code }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/change/phone/code`, { phone, code })
.then((response) =>
{
console.log("\n", "sendPhoneChangeNumberSmsCode", "RESPONSE");
console.log(response.data);
if(response.data.status === "success")
{
resolve();
}
else
{
reject();
}
})
.catch((error) =>
{
console.error("error");
console.error(error);
reject();
});
});
}
export const sendNewPassword = ({ email, password, new_password, new_password_repeat }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/change/password`, { email, password, new_password, new_password_repeat })
.then((response) =>
{
console.log("\n", "sendNewPassword", "RESPONSE");
console.log(response.data);
if(response.data)
{
resolve(response.data);
}
else
{
reject();
}
})
.catch((error) =>
{
console.error("error");
console.error(error);
reject();
});
});
}
export const setUserPhone = ({ dispatch, user }) =>
{
console.log("setUserPhone", dispatch, user);
dispatch({ type: actionTypes.USER, data: user });
}

View File

@ -12,4 +12,5 @@ export const CONTRACT_TELEMATIC = 'CONTRACT_TELEMATIC';
export const CONTRACT_AGREEMENT = 'CONTRACT_AGREEMENT';
export const CONTRACT_DOCUMENTS = 'CONTRACT_DOCUMENTS';
export const CONTRACT_RULES = 'CONTRACT_RULES';
export const CONTRACT_MATERIALS = 'CONTRACT_MATERIALS';
export const CALENDAR = 'CALENDAR';

File diff suppressed because one or more lines are too long

View File

@ -3313,6 +3313,24 @@ main .dropdown_blocks_list .dropdown_block .block_body {
}
}
}
.fines_detail {
margin: 30px 0;
p {
margin-bottom: 10px;
}
ul {
max-width: 100%;
display: flex;
flex-wrap: wrap;
li {
width: 50%;
}
}
}
}
.dropdown_block + .dosc_list {

View File

@ -464,3 +464,19 @@ div {
padding-bottom: 10px;
}
}
.rw-calendar-btn-view {
font-weight: bold;
font-size: 12px !important;
}
@media all and (max-width: 768px) {
.rw-calendar-btn-view {
font-size: 12px !important;
line-height: 20px;
}
}
@media (max-width: 1600px) and (min-width: 1280px) {
.rw-calendar-btn-view {
font-size: 12px !important;
line-height: 20px;
}
}

View File

@ -493,4 +493,19 @@ div {
padding-bottom: 10px;
}
}
}
.rw-calendar-btn-view {
font-weight: bold;
font-size: 12px !important;
@media all and (max-width: 768px) {
font-size: 12px !important;
line-height: 20px;
}
@media (max-width: 1600px) and (min-width: 1280px) {
font-size: 12px !important;
line-height: 20px;
}
}

View File

@ -44,18 +44,22 @@ module.exports = withImages(withFonts(withLess({
{
source: "/contract/:number/payments",
destination: "/contract",
},
},
{
source: "/contract/:number/agreement",
destination: "/contract/agreement",
},
},
{
source: "/contract/:number/services",
destination: "/contract/services",
},
},
{
source: "/contract/:number/documents",
destination: "/contract/documents",
},
{
source: "/contract/:number/materials",
destination: "/contract/materials",
}
//{
//source: "/about/reviews/:page(\\d{1,})",

View File

@ -15,7 +15,8 @@ const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
const { phone } = req.body;
let { phone } = req.body;
phone = phone.replace(/[^0-9.]/g, '');
const response = await new Promise((resolve, reject) =>
{

View File

@ -13,9 +13,11 @@ const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
const { phone, code } = req.body;
let { phone, code } = req.body;
let token = "";
phone = phone.replace(/[^0-9.]/g, '');
const key = md5(`sms_code_${ phone }`);
let existed_data = await redis.get(key);

View File

@ -0,0 +1,50 @@
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import Redis from 'ioredis';
import md5 from 'md5';
import { cors } from '../../../lib/cors';
const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
const { email, password, new_password, new_password_repeat } = req.body;
const response = await new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_API_HOST }/api/account/change/password/`, {
email,
password,
new_password,
new_password_repeat
})
.then((api_response) =>
{
console.log("RESPONSE");
console.log(api_response.data);
resolve(api_response.data);
})
.catch((error) =>
{
console.log("error");
console.error(error);
res.status(403).json();
});
});
if(response)
{
res.status(200).json(response);
}
else
{
res.status(403).json();
}
}

View File

@ -0,0 +1,81 @@
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import Redis from 'ioredis';
import md5 from 'md5';
import { cors } from '../../../../lib/cors';
const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
let { phone, code } = req.body;
let token = "";
phone = phone.replace(/[^0-9.]/g, '');
const key = md5(`phone_change_sms_code_${ phone }`);
let existed_data = await redis.get(key);
if(existed_data !== null)
{
const existed_data_json = JSON.parse(existed_data);
if(existed_data_json.code === code)
{
console.log("existed_data_json");
console.log(existed_data_json);
console.log("*".repeat(50));
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
const response = await new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_API_HOST }/api/account/change/phone/`, {
email: existed_data_json.email,
phone,
}, {
headers: {
"Authorization": `Bearer ${ cookies.jwt }`,
},
})
.then((api_response) =>
{
console.log("RESPONSE FROM API");
console.log(api_response.data);
resolve({ status: "success" });
})
.catch((error) =>
{
console.log("RESPONSE FROM API");
console.error("error");
console.error(error);
resolve({ status: "error" });
});
});
if(response.status === "success")
{
res.status(200).json({ status: "success", });
}
else
{
res.status(403).json();
}
}
else
{
res.status(403).json();
}
}
else
{
res.status(403).json();
}
}

View File

@ -0,0 +1,42 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import Redis from 'ioredis';
import md5 from 'md5';
import { cors } from '../../../../lib/cors';
const SmsCenter = require('../../../../lib/SmsCenter');
const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
let { email, phone } = req.body;
phone = phone.replace(/[^0-9.]/g, '');
let code = ``;
for(let i = 0; i < 6; i++) { code = `${code}${Math.floor(Math.random()*10)}`; }
const key = md5(`phone_change_sms_code_${ phone }`);
await redis.set(key, JSON.stringify({
email, phone, code
}), 'EX', 300);
const smsResult = await SmsCenter.send(phone, code)
.then(() =>
{
res.status(200).json({
status: "success",
});
})
.catch((error) =>
{
console.error("SmsCenter.send", "catch");
res.status(404).json();
});
}

View File

@ -0,0 +1,52 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import { cors } from '../../../lib/cors';
export default async function handler(req, res)
{
await cors(req, res);
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
{
if(jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT))
{
const response = await new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_API_HOST }/api/account/materials/`, {
})
.then((api_response) =>
{
console.log("RESPONSE");
console.log(api_response.data);
resolve(api_response.data);
})
.catch((error) =>
{
console.log("error");
console.error(error);
reject([]);
});
});
res.status(200).json(response);
}
else
{
res.status(403);
}
}
else
{
res.status(403);
}
}
}

View File

@ -0,0 +1,52 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import { cors } from '../../../lib/cors';
export default async function handler(req, res)
{
await cors(req, res);
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
{
var client_jwt_decoded = jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT);
var crm_jwt = jwt.sign(client_jwt_decoded, process.env.JWT_SECRET_CRM, { noTimestamp: true });
try
{
axios.get(`${ process.env.CRM_API_HOST }/lk/Contract/GetInvoiceFineGIBDDAfterAccrual/`, {
params: { ...client_jwt_decoded, contract_number: req.query.contract, decree_number: req.query.num, },
responseType: 'arraybuffer',
headers: {
"Authorization": `Bearer ${ crm_jwt }`,
}
})
.then((crm_response) =>
{
res.status(200).send(crm_response.data);
})
.catch((error) =>
{
console.error(error);
res.status(500);
});
}
catch(e)
{
console.error(e);
res.status(500);
}
}
else
{
res.status(403);
}
}
}

View File

@ -0,0 +1,52 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import { cors } from '../../../lib/cors';
export default async function handler(req, res)
{
await cors(req, res);
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
{
var client_jwt_decoded = jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT);
var crm_jwt = jwt.sign(client_jwt_decoded, process.env.JWT_SECRET_CRM, { noTimestamp: true });
try
{
axios.get(`${ process.env.CRM_API_HOST }/lk/Contract/GetInvoiceFineGIBDDBeforeAccrual/`, {
params: { ...client_jwt_decoded, contract_number: req.query.contract, decree_number: req.query.num, },
responseType: 'arraybuffer',
headers: {
"Authorization": `Bearer ${ crm_jwt }`,
}
})
.then((crm_response) =>
{
res.status(200).send(crm_response.data);
})
.catch((error) =>
{
console.error(error);
res.status(500);
});
}
catch(e)
{
console.error(e);
res.status(500);
}
}
else
{
res.status(403);
}
}
}

View File

@ -0,0 +1,61 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import Redis from 'ioredis';
import md5 from 'md5';
import { cors } from '../../../lib/cors';
const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
const { email } = req.body;
let code = ``;
for(let i = 0; i < 10; i++) { code = `${code}${Math.floor(Math.random()*10)}`; }
const response = await new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_API_HOST }/api/account/recovery/email/`, {
email,
code,
})
.then((api_response) =>
{
console.log("RESPONSE");
console.log(api_response.data);
resolve(api_response.data);
})
.catch((error) =>
{
console.log("error");
console.error(error);
reject();
});
});
console.log("CHECK response");
console.log(response);
console.log("-".repeat(50));
if(response.status === "success")
{
const key = md5(`email_code_${ email }`);
await redis.set(key, JSON.stringify({ code }), 'EX', 300);
res.status(200).json({
status: "success",
});
}
else
{
res.status(404).json();
}
}

View File

@ -0,0 +1,40 @@
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import Redis from 'ioredis';
import md5 from 'md5';
import { cors } from '../../../lib/cors';
const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
const { email, code, } = req.body;
let token = "";
const key = md5(`email_code_${ email }`);
let existed_data = await redis.get(key);
if(existed_data !== null)
{
const existed_data_json = JSON.parse(existed_data);
if(existed_data_json.code === code)
{
await redis.set(key, JSON.stringify({ code }), 'EX', 900);
res.status(200).json({ status: "success" });
}
else
{
res.status(403).json();
}
}
else
{
res.status(403).json();
}
}

View File

@ -0,0 +1,66 @@
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import Redis from 'ioredis';
import md5 from 'md5';
import { cors } from '../../../lib/cors';
const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
const { email, code, password, password_repeat } = req.body;
let token = "";
const key = md5(`email_code_${ email }`);
let existed_data = await redis.get(key);
if(existed_data !== null)
{
const existed_data_json = JSON.parse(existed_data);
if(existed_data_json.code === code && password === password_repeat)
{
const response = await new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_API_HOST }/api/account/recovery/password/`, {
email,
code,
password,
password_repeat
})
.then((api_response) =>
{
console.log("RESPONSE");
console.log(api_response.data);
resolve(api_response.data);
})
.catch((error) =>
{
console.log("error");
console.error(error);
res.status(403).json();
});
});
if(response)
{
res.status(200).json({ status: "success" });
}
}
else
{
res.status(403).json();
}
}
else
{
res.status(403).json();
}
}

View File

@ -0,0 +1,53 @@
import React from "react";
import { SpinnerCircular } from 'spinners-react';
import { getFineBeforeAccrualFile, getFineAfterAccrualFile } from "../../../actions";
export default class DownloadFinesPdfButton extends React.Component
{
constructor(props)
{
super(props);
this.state = {
downloading: false,
};
}
_handle_onDownloadFile = () =>
{
const { contract, num, filename, before } = this.props;
const { downloading } = this.state;
if(!downloading)
{
this.setState({ downloading: true }, () =>
{
if(before)
{
getFineBeforeAccrualFile({ contract, num, filename })
.then(() => { this.setState({ downloading: false }); })
.catch(() => { this.setState({ downloading: false }); });
}
else
{
getFineAfterAccrualFile({ contract, num, filename })
.then(() => { this.setState({ downloading: false }); })
.catch(() => { this.setState({ downloading: false }); });
}
});
}
}
render()
{
const { downloading } = this.state;
return (
<a style={{ cursor: "pointer" }} className="button button-blue" onClick={ () => { this._handle_onDownloadFile() }}>
{ downloading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" />
) : "Скачать" }
</a>
)
}
}

View File

@ -24,7 +24,7 @@ export default class Header extends React.Component
{
if(route === "/") return "Договоры";
if(route.indexOf("/documents/") > -1) return "Взаиморасчеты и закрывающие документы";
if(route === "/settings") return "Настройки";
if(route.indexOf("/settings/") > -1) return "Настройки";
if(route.indexOf("/contract") === 0) return "Договоры";
return null;
@ -61,8 +61,8 @@ export default class Header extends React.Component
</Link>
</li>
<li>
<Link href="/settings/" shallow>
<a className={ this.props.router && this.props.router.route === "/settings" ? "active" : "" }>Настройки</a>
<Link href="/settings/phone/" shallow>
<a className={ this.props.router && this.props.router.route.indexOf("/settings/") === 0 ? "active" : "" }>Настройки</a>
</Link>
</li>
<li>

View File

@ -122,7 +122,7 @@ class ContractPage extends React.Component
<div className="row" key={ file_index }>
<p className="doc_name i-pdf extension" data-format={ file.extension }>
{ types[document.type] }
<span style={{ width: "100%"}}>{ file.number } от { file.date }</span>
<span style={{ width: "100%"}}>{ file.number } от { moment(file.date).format("DD.MM.YYYY") }</span>
{ file.type !== undefined && <span>{ file.type }</span> }
</p>
<DownloadPdfButton id={ file.url } filename={ `evoleasing_${ document.type }_${ file.number }_${ file.date }.${ file.extension }` }/>

View File

@ -13,7 +13,7 @@ export default class InnerMenu extends React.Component
{
let l = 0;
let m = 0;
const menu = ["payments", "services", "agreement", "documents"];
const menu = ["payments", "services", "agreement", "documents", "materials"];
for(let i in menu)
{
@ -50,12 +50,17 @@ export default class InnerMenu extends React.Component
</li>
<li>
<Link href={`/contract/${ number }/agreement`} shallow>
<a className={ this.props.router && this.props.router.asPath.indexOf("agreement") > -1 ? "active" : "" }>Договор</a>
<a className={ this.props.router && this.props.router.asPath.indexOf("agreement") > -1 ? "active" : "" }>Документы по договору</a>
</Link>
</li>
<li>
<Link href={`/contract/${ number }/documents`} shallow>
<a className={ this.props.router && this.props.router.asPath.indexOf("documents") > -1 ? "active" : "" }>Документы по сделке</a>
<a className={ this.props.router && this.props.router.asPath.indexOf("documents") > -1 ? "active" : "" }>Закрывающие документы</a>
</Link>
</li>
<li>
<Link href={`/contract/${ number }/materials`} shallow>
<a className={ this.props.router && this.props.router.asPath.indexOf("materials") > -1 ? "active" : "" }>Документы по ФСБУ 25/2018</a>
</Link>
</li>
</ul>

View File

@ -16,6 +16,8 @@ import InnerMenu from "./components/InnerMenu";
import Company from "../components/Company";
import DateInput from '../components/DatePicker';
import DownloadPrintFormPdfButton from "../components/DownloadPrintFormPdfButton";
import DownloadFinesPdfButton from "../components/DownloadFinesPdfButton";
import { getContractInfo, getContractDocuments, getReconciliationFile } from "../../actions";
@ -253,47 +255,68 @@ class ContractDocumentsPage extends React.Component
_renderFines = (fines, type) =>
{
console.log("_renderFines");
console.log("fines");
console.log(fines);
const { number } = this.props;
const { opened } = this.state;
const status = {
"NotPaid": "danger",
"PaidEvolution": "success",
"PaidIndependently": "success",
};
if(fines.length > 0)
{
<>
<div className={`dropdown_block bt ${ opened.indexOf('fines') > -1 ? "open" : "" }`}>
<div className="block_header" onClick={ () => this._handle_onGroup('fines') }>
<p>{ TYPES[ 'fines' ] }</p>
{ fines.length > 3 && (
<button className={`block_toggle ${ opened.indexOf('fines') > -1 ? "rotate" : "" }`}></button>
return (
<>
<div className={`dropdown_block bt ${ opened.indexOf('fines') > -1 ? "open" : "" }`}>
<div className="block_header" onClick={ () => this._handle_onGroup('fines') }>
<p>{ TYPES[ 'fines' ] }</p>
{ fines.length > 3 && (
<button className={`block_toggle ${ opened.indexOf('fines') > -1 ? "rotate" : "" }`}></button>
) }
</div>
</div>
<div className="dropdown_blocks_list">
{ fines.slice(0, opened.indexOf('fines') > -1 ? fines.length : 3).map((fine, fine_index) => (
<div className="dropdown_block open" key={ fine_index }>
<div className="block_body" >
<div className="fines_detail">
<p> постановления: <b>{ fine.fine_number }</b></p>
<ul>
<li>Дата нарушения: <b>{ moment(fine.violation_date).format("DD.MM.YYYY") }</b></li>
{ fine.violation_place !== undefined && fine.violation_place !== null && (<li>Место нарушения: <b>{ fine.violation_place }</b></li>)}
<li>Сумма: <b style={{ whiteSpace: "nowrap" }}>{ numeral(fine.amount).format(' ., ') }&nbsp;</b></li>
{ fine.gibdd !== undefined && fine.gibdd !== null && (<li>Подразделение: <b>{ fine.gibdd }</b></li>)}
<li>Статус: <b className={ status[fine.status_code] }>{ fine.status }</b></li>
</ul>
<p style={{ paddingTop: "15px" }}>Штраф: { fine.fine_title }</p>
</div>
{ fine.status_code !== "PaidIndependently" && (
<div className="dosc_list medium-icon">
<div className="row">
<p className="doc_name i-pdf i-medium" style={{ marginBottom: "0px" }}>Счет на оплату</p>
<DownloadFinesPdfButton className="download-icon" filename={ `evolution_fine_${ fine.fine_number }.pdf` } before={ fine.status_code === "NotPaid" ? true : false } contract={ number } num={ fine.fine_number }/>
</div>
</div>
) }
</div>
{/*}
<DownloadPrintFormPdfButton className="download-icon" filename={ `${ number }_${ upd_document.type }_${ upd_document.num }.pdf` } contract={ number } num={ upd_document.num } date={ upd_document.date } type={ upd_document.type }/>
{*/}
</div>
)) }
{ opened.indexOf(type) < 0 && fines.length > 3 && (
<div className="row" style={{ display: "flex", justifyContent: "center", corsor: "pointer" }} onClick={ () => this._handle_onGroup(type) }>
<p style={{ paddingTop: "15px", color: "#747474" }}>Еще { fines.length - 3 } { pluralize((fines.length - 3), 'постановлений', 'постановление', 'постановления', 'постановлений') }</p>
</div>
) }
</div>
</div>
<div className="dosc_list medium-icon">
{ fines.slice(0, opened.indexOf('fines') > -1 ? fines.length : 3).map((fine, fine_index) => (
<div className="row" key={ fine_index }>
<div className="block_body" >
<div className="transaction_detail">
<p> постановления: <b>{ fine.fine_number }</b></p>
<ul>
<li>Сумма: <b style={{ whiteSpace: "nowrap" }}>{ numeral(fine.amount).format(' ., ') }&nbsp;</b></li>
<li>Дата: <b>{ moment(fine.fine_date).format("DD.MM.YYYY") }</b></li>
<li>Статус: <b className="success">{ fine.status }</b></li>
<li>Штраф: { fine.fine_title }</li>
</ul>
</div>
</div>
{ opened.indexOf(type) < 0 && fines.length > 3 && (
<div className="row" style={{ justifyContent: "center", corsor: "pointer" }} onClick={ () => this._handle_onGroup(type) }>
<p style={{ color: "#747474" }}>Еще { fines.length - 3 } { pluralize((fines.length - 3), 'постановлений', 'постановление', 'постановления', 'постановлений') }</p>
</div>
) }
{/*}
<DownloadPrintFormPdfButton className="download-icon" filename={ `${ number }_${ upd_document.type }_${ upd_document.num }.pdf` } contract={ number } num={ upd_document.num } date={ upd_document.date } type={ upd_document.type }/>
{*/}
</div>
)) }
</div>
</>
</>
)
}
return (

149
pages/contract/materials.js Normal file
View File

@ -0,0 +1,149 @@
import React from "react";
import Head from 'next/head';
import Image from 'next/image';
import { connect } from "react-redux";
import { withRouter } from 'next/router';
import moment from "moment";
import { SpinnerCircular } from 'spinners-react';
import { reduxWrapper } from '../../store';
import Header from '../components/Header';
import Footer from '../components/Footer';
import Company from "../components/Company";
import InnerMenu from "./components/InnerMenu";
import DownloadPdfButton from "../components/DownloadPdfButton";
import { getContractInfo, getContractMaterials } from "../../actions";
class ContractPage extends React.Component
{
constructor(props)
{
super(props);
this.state = {
date: null,
car: null,
contract_date: null,
materials: null,
loading: false,
}
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
date: nextProps.date,
car: nextProps.car,
contract_date: nextProps.contract_date,
materials: nextProps.materials,
};
}
componentDidMount()
{
if(!this.state.loading && this.props.number !== undefined)
{
this.setState({ loading: true }, () =>
{
getContractInfo({ dispatch: this.props.dispatch, number: this.props.number }).then((info) =>
{
console.log("info", info);
getContractMaterials({
dispatch: this.props.dispatch,
}).then(() => {
this.setState({ loading: false })
}).catch(() => {});
})
.catch(() =>
{
});
});
}
}
render()
{
const { loading, date, car, materials, } = this.state;
const { number } = this.props;
console.log("materials", materials);
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta
name="description"
content="ЛК Эволюция автолизинга"
/>
</Head>
<Header { ...this.props }/>
<main>
<section>
<div className="clear"></div>
<div className="container">
<div className="title_wrapper">
<div className="left" style={{ flexDirection: 'column', }}>
<h1 className="section_title">Договор { number }</h1>
<h5 style={{ fontSize: '14px' }}>{ date !== undefined && date !== null && date !== null && (<> от { moment(date).format("DD.MM.YYYY") }</>)}{ car !== undefined && car !== null ? ` - ${ car.brand.name } ${ car.model.name } | ${ car.reg_number !== null ? car.reg_number : 'без рег. номера' } | ${ car.vin_number !== null ? car.vin_number : 'без VIN номера' }` : '' }</h5>
</div>
<Company/>
</div>
<div className="aside_container about">
<InnerMenu number={ number } { ...this.props }/>
<article>
{ loading ? (
<div className="table_row table_header" style={{ minHeight: 300, display: "flex", justifyContent: "center", alignItems: "center" }}>
<SpinnerCircular size={90} thickness={51} speed={100} color="rgba(28, 1, 169, 1)" secondaryColor="rgba(236, 239, 244, 1)" />
</div>
) : (
<div className="dosc_list">
{ materials !== undefined && materials !== null && materials.map((document, index ) => (
<div className="row" key={ index }>
<p className="doc_name i-pdf">
{ document.name }
{ document.description !== null && document.description !== "" && (
<span style={{ width: "100%"}}>{ document.description }</span>
) }
</p>
<DownloadPdfButton url={ `${ process.env.NEXT_PUBLIC_MAIN_SITE }${ document.url }` } filename={ `${ document.filename }.pdf` } bitrix={ true }/>
</div>
)) }
</div>
) }
</article>
</div>
</div>
</section>
</main>
<Footer/>
</React.Fragment>
);
}
}
function mapStateToProps(state, ownProps)
{
return {
contract_date: state.contract.date,
date: state.contract.date,
car: state.contract.car,
materials: state.contract.materials,
}
}
export const getServerSideProps = reduxWrapper.getServerSideProps(store =>
async ({ req, res, query }) =>
{
return {
props: {
number: query.number,
}
}
}
);
export default withRouter(connect(mapStateToProps)(ContractPage));

View File

@ -29,6 +29,7 @@ class LoginPage extends React.Component
email_error: false,
phone_form_step: 1,
phone_number_error: false,
phone_number_format_error: false,
phone_code_error: false,
email_login_disabled: true,
phone_login_disabled: true,
@ -158,7 +159,8 @@ class LoginPage extends React.Component
_handle_onPhoneChange = (value) =>
{
this.setState({ phone: value, phone_login_disabled: this._check_fields_disabled([ value ]), phone_number_error: false });
const phone_number_format_error = value.length > 1 && (value[0] !== "+" || value[1] !== "7") ? true : false;
this.setState({ phone: value, phone_login_disabled: this._check_fields_disabled([ value ]), phone_number_error: false, phone_number_format_error: phone_number_format_error });
}
_handle_onPhoneCodeChange = (value) =>
@ -166,6 +168,27 @@ class LoginPage extends React.Component
this.setState({ phone_code: value, phone_code_submit_disabled: this._check_fields_disabled([ value ]), phone_sms_code_error: false });
}
_handle_onRecover = (event) =>
{
event.preventDefault();
this.props.router.push("/recovery");
}
_handle_onEmailFormFieldKey = (event) =>
{
const { email_login_disabled } = this.state;
if(event.keyCode === 13)
{
event.preventDefault();
if(!email_login_disabled)
{
this._handle_onEmailSubmit(event);
}
}
}
_check_fields_disabled = (values) =>
{
for(let i in values)
@ -183,7 +206,7 @@ class LoginPage extends React.Component
{
const { email, password, phone, phone_code, tab, email_error, phone_number_error, phone_code_error,
email_login_disabled, phone_login_disabled, phone_form_step, phone_code_submit_disabled, phone_code_resend_disabled,
timer, phone_sms_code_error, email_auth_loading, phone_check_loading, code_check_loading } = this.state;
timer, phone_sms_code_error, email_auth_loading, phone_check_loading, code_check_loading, phone_number_format_error } = this.state;
return (
<React.Fragment>
@ -211,16 +234,23 @@ class LoginPage extends React.Component
{ tab === "email" ? (
<form onSubmit={ this._handle_onEmailSubmit }>
<div className="form_field">
<input type="text" name="email" value={ email } placeholder="Введите адрес E-mail" onChange={ (event) => this._handle_onEmailChange(event.target.value) } required={ true }/>
<input type="text" name="email" value={ email } placeholder="Введите адрес E-mail" onChange={ (event) => this._handle_onEmailChange(event.target.value) } onKeyDown={ this._handle_onEmailFormFieldKey } required={ true }/>
</div>
<div className="form_field">
<input type="password" name="password" value={ password } placeholder="Введите пароль" onChange={ (event) => this._handle_onPasswordChange(event.target.value) } required={ true }/>
<input type="password" name="password" value={ password } placeholder="Введите пароль" onChange={ (event) => this._handle_onPasswordChange(event.target.value) } onKeyDown={ this._handle_onEmailFormFieldKey } required={ true }/>
</div>
<div style={{ display: "flex", flexDirection: "row", justifyContent: "flex-end", alignItems: "flex-end" }}>
<div>
<button className="button button-blue transparent" style={{ width: "160px" }} onClick={ (event) => this._handle_onRecover(event) }>Забыли пароль?</button>
</div>
<div>
<button type="submit" className="button button-blue" disabled={ email_login_disabled }>
{ email_auth_loading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
) : "Войти" }
</button>
</div>
</div>
<button type="submit" className="button button-blue" disabled={ email_login_disabled }>
{ email_auth_loading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
) : "Войти" }
</button>
<p>{ email_error ? 'Ошибка: Неверный логин или пароль' : `\u00A0` }</p>
</form>
) : (
@ -238,11 +268,12 @@ class LoginPage extends React.Component
</button>
</form>
<p>{ phone_number_error ? 'Ошибка: нет аккаунта с таким номером телефона' : `\u00A0` }</p>
<p>{ phone_number_format_error ? 'Ошибка: номер должен начинаться на +7' : `\u00A0` }</p>
</>
) }
{ phone_form_step === 2 && (
<>
<p className="message">На номер <strong>+{ phone }</strong> отправлен код подтверждения.</p>
<p className="message">На номер <strong>+{ phone.replace(/[^0-9.]/g, '') }</strong> отправлен код подтверждения.</p>
<form onSubmit={ this._handle_onCodeSubmit }>
<div className="form_field">
<input type="text" name="phone_code" value={ phone_code } placeholder="Введите код из СМС" onChange={ (event) => this._handle_onPhoneCodeChange(event.target.value) } />

View File

@ -1,102 +1,289 @@
import React from "react";
import Head from 'next/head';
import Link from "next/link";
import Image from 'next/image';
import { connect } from "react-redux";
import { withRouter } from 'next/router';
import { reduxWrapper } from '../store';
import pluralize from 'pluralize-ru';
import { SpinnerCircular } from 'spinners-react';
import Header from './components/Header';
import Footer from './components/Footer';
import MainHeader from "./components/MainHeader";
import FormRequest from "./components/FormRequest";
export default function RecoveryPassPage() {
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta
name="description"
content="ЛК Эволюция автолизинга"
/>
</Head>
<Header />
<main>
<section>
<div className="clear"></div>
<div className="container">
<h1 className="section_title">Восстановление пароля</h1>
<div className="login recovery">
<div className="login_with">
<p>Восстановить с помощью</p>
<div className="tabs">
<div className="tab active">Номера телефона</div>
<div className="tab">Электронной почты</div>
import { sendEmailCheck, sendEmailCode, sendPassword } from "../actions/recoveryActions";
export default class RecoveryPage extends React.Component
{
constructor(props)
{
super(props);
this.state = {
email: "",
email_code: "",
password: "",
password_repeat: "",
tab: "email",
email_error: false,
recovery_form_step: 1,
email_code_error: false,
email_send_disabled: false,
set_password_disabled: true,
email_code_submit_disabled: true,
email_code_resend_disabled: true,
timer: 0,
email_auth_loading: false,
email_check_loading: false,
code_check_loading: false,
password_check_loading: false,
password_submit_disabled: true,
password_error: false,
};
this.timer_ref = null;
}
_handle_onEmailSubmit = (event) =>
{
event.preventDefault();
const { email, email_check_loading } = this.state;
if(!email_check_loading)
{
this.setState({ email_check_loading: true }, () =>
{
sendEmailCheck({ email })
.then(() =>
{
this.setState({ email_check_loading: false, email_error: false, timer: 60, recovery_form_step: 2, }, () =>
{
this.timer_ref = setInterval(() =>
{
const t = this.state.timer - 1;
this.setState({ timer: t, }, () =>
{
if(t === 0)
{
clearInterval(this.timer_ref);
}
});
}, 1000);
});
})
.catch(() =>
{
this.setState({ email_error: true, email_check_loading: false });
});
});
}
}
_handle_onCodeSubmit = (event) =>
{
event.preventDefault();
const { email, email_code, code_check_loading } = this.state;
if(!code_check_loading)
{
this.setState({ code_check_loading: true }, () =>
{
sendEmailCode({ email, code: email_code })
.then(() =>
{
this.setState({ email_code_error: false, code_check_loading: false, recovery_form_step: 3, });
})
.catch(() =>
{
this.setState({ email_code_error: true, code_check_loading: false });
});
});
}
}
_handle_onPasswordSubmit = (event) =>
{
event.preventDefault();
const { email, email_code, password, password_repeat, password_check_loading, } = this.state;
if(!password_check_loading)
{
this.setState({ password_check_loading: true }, () =>
{
sendPassword({ email, code: email_code, password, password_repeat, })
.then(() =>
{
this.setState({ password_error: false, password_check_loading: false, recovery_form_step: 4, });
})
.catch(() =>
{
this.setState({ password_error: true, password_check_loading: false });
});
});
}
}
_handle_onResendEmailCode = (event) =>
{
this.setState({ email_code_error: false }, () =>
{
this._handle_onEmailSubmit(event);
});
}
_handle_onEmailChange = (value) =>
{
this.setState({ email: value, email_send_disabled: this._check_fields_disabled([ value, this.state.email ]), email_error: false });
}
_handle_onPasswordChange = (value) =>
{
this.setState({ password: value, set_password_disabled: this._check_fields_disabled([ value, this.state.password ]), password_error: false });
}
_handle_onPasswordCheckChange = (value) =>
{
this.setState({ password_repeat: value, set_password_disabled: this._check_fields_disabled([ value, this.state.password_repeat ]), password_error: false });
}
_handle_onEmailCodeChange = (value) =>
{
this.setState({ email_code: value, email_code_submit_disabled: this._check_fields_disabled([ value ]), email_code_error: false });
}
_handle_onPasswordChange = (value) =>
{
let error = value === this.state.password_repeat ? false : true;
error = value.length < 6 ? true : error;
this.setState({ password: value, password_submit_disabled: error ? true : this._check_fields_disabled([ value ]), password_error: error });
}
_handle_onPasswordRepeatChange = (value) =>
{
let error = value === this.state.password ? false : true;
error = value.length < 6 ? true : error;
this.setState({ password_repeat: value, password_submit_disabled: error ? true : this._check_fields_disabled([ value ]), password_error: error });
}
_check_fields_disabled = (values) =>
{
for(let i in values)
{
if(values[i] === "")
{
return true;
}
}
return false;
}
render()
{
const { email, email_code, tab, email_error, email_code_error,
set_password_disabled, email_send_disabled, recovery_form_step, email_code_submit_disabled, email_code_resend_disabled,
timer, email_auth_loading, email_check_loading, code_check_loading,
password, password_repeat, password_submit_disabled, password_check_loading, password_error } = this.state;
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta
name="description"
content="ЛК Эволюция автолизинга"
/>
</Head>
<MainHeader logo_url={ process.env.NEXT_PUBLIC_MAIN_SITE } />
<main>
<section>
<div className="clear"></div>
<div className="container">
<h1 className="section_title">Восстановление пароля</h1>
<div className="login recovery">
<div className="login_with">
<p>Восстановить с помощью</p>
<div className="tabs">
<div className="tab active">Электронной почты</div>
</div>
</div>
{ recovery_form_step === 1 && (
<>
<form onSubmit={ this._handle_onEmailSubmit }>
<div className="form_field">
<input type="text" name="email" value={ email } placeholder="Введите адрес E-mail" onChange={ (event) => this._handle_onEmailChange(event.target.value) } required={ true }/>
</div>
<button type="submit" className="button button-blue" disabled={ email_send_disabled }>
{ email_auth_loading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
) : "Получить код" }
</button>
</form>
<p>{ email_error ? 'Ошибка: Неверный адрес E-mail' : `\u00A0` }</p>
</>
) }
{ recovery_form_step === 2 && (
<>
<p className="message">На адрес E-mail <strong>{ email }</strong> отправлен код подтверждения.</p>
<form onSubmit={ this._handle_onCodeSubmit }>
<div className="form_field">
<input type="text" name="email_code" value={ email_code } placeholder="Введите код из письма" onChange={ (event) => this._handle_onEmailCodeChange(event.target.value) } />
</div>
<button type="submit" className="button button-blue" disabled={ email_code_submit_disabled }>
{ code_check_loading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
) : "Отправить код" }
</button>
</form>
<div className="resend" style={{ justifyContent: "flex-start" }}>
{ timer !== 0 ? (
<p>Запросить код повторно можно через: { timer } { pluralize(timer, 'секунд', 'секунду', 'секунды', 'секунд') }</p>
) : (
<button className="button button-blue transparent" onClick={ (event) => this._handle_onResendEmailCode(event) }>Запросить код повторно</button>
) }
{/* disabled={ email_code_resend_disabled }*/}
</div>
<p>{ email_code_error ? 'Ошибка: Вы указали неверный код' : `\u00A0` }</p>
</>
) }
{ recovery_form_step === 3 && (
<>
<p className="message">Вы указали верный код. Введите новый пароль и повторите новый пароль</p>
<form onSubmit={ this._handle_onPasswordSubmit } className="newPass_form">
<div className="form_field">
<input type="password" name="password" value={ password } placeholder="Введите новый пароль" onChange={ (event) => this._handle_onPasswordChange(event.target.value) } />
</div>
<div className="form_field">
<input type="password" name="password_repeat" value={ password_repeat } placeholder="Повторите новый пароль" onChange={ (event) => this._handle_onPasswordRepeatChange(event.target.value) } />
</div>
<button type="submit" className="button button-blue" disabled={ password_submit_disabled }>
{ password_check_loading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
) : "Изменить пароль" }
</button>
</form>
<p>{ password_error ? 'Внимание: пароли не совпадают' : `\u00A0` }</p>
<p>{ password.length < 6 ? 'Внимание: минимальная длина пароля - 6 символов' : `\u00A0` }</p>
</>
) }
{ recovery_form_step === 4 && (
<>
<p className="message">Поздравляем, пароль успешно изменен. <Link href="/login"><a style={{ cursor: "pointer" }}>Нажмите здесь для авторизации</a></Link>.</p>
</>
) }
</div>
<form>
<div className="form_field">
<input type="text" name="login" value="" placeholder="Введите номер телефона" />
</div>
<button type="submit" className="button button-blue" disabled>Получить код</button>
</form>
{/* Step 2
<p className="message">На указанный номер отправлен код.</p>
<form>
<div className="form_field">
<input type="text" name="pass" value="" placeholder="Введите код из СМС" />
</div>
<button type="submit" className="button button-blue" disabled>Отправить код</button>
</form>
<div className="resend">
<p>Запросить код повторно можно через: 60 секунд</p>
<button className="button button-blue transparent" disabled>Запросить код повторно</button>
</div>
*/}
{/* Step 3 */}
{/*<form className="newPass_form">
<div className="form_field">
<input type="text" name="new_pass" value="" placeholder="Введите новый пароль" />
</div>
<div className="form_field">
<input type="text" name="new_pass" value="" placeholder="Повторите новый пароль" />
</div>
<button type="submit" className="button button-blue" disabled>Сохранить</button>
</form>*/}
</div>
</div>
</section>
<section id="order">
<div className="container wide">
<h2 className="section_title">Купить в лизинг?</h2>
<div className="order_form">
<div className="order_email">
<p>Напишите на <a href="mailto:">buy@domain.ru</a> или заполните форму</p>
</div>
<form>
<div className="form_field">
<input type="text" value="" placeholder="Имя" />
</div>
<div className="form_field">
<input type="tel" value="" placeholder="Телефон" />
</div>
<div className="form_field">
<input type="email" value="" placeholder="E-mail" />
</div>
<div className="form_field">
<input type="text" value="" placeholder="Организация" />
</div>
<div className="policy">
<input type="checkbox" name="policy" id="policy" hidden checked />
<label for="policy">Даю свое согласие на обработку моих персональных данных</label>
</div>
<button className="button">Отправить</button>
</form>
</div>
</div>
</section>
</main>
<Footer />
</React.Fragment>
)
}
</section>
<FormRequest/>
</main>
<Footer />
</React.Fragment>
)
}
}

View File

@ -1,114 +0,0 @@
import React from "react";
import Head from 'next/head';
import Image from 'next/image';
import Link from "next/link";
import cookie from 'cookie';
import { connect } from "react-redux";
import numeral from "numeral";
import { withRouter } from 'next/router';
import { reduxWrapper } from '../store';
import Header from './components/Header';
import Footer from './components/Footer';
import Pagination from './components/Pagination';
import { logout } from '../actions';
class IndexPage extends React.Component
{
constructor(props)
{
super(props);
this.state = {
};
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
};
}
_handle_onLogout = () =>
{
logout({ dispatch: this.props.dispatch }).then().catch();
}
render()
{
const { loading, page, pages, search, date_from, date_from_type, date_to, date_to_type, contracts, sort_number, sort_date, sort_status } = this.state;
console.log("contracts");
console.log(contracts);
const contract_status = {
"Действующий": "opened",
"Закрыт": "closed",
};
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta
name="description"
content="ЛК Эволюция автолизинга"
/>
</Head>
<Header { ...this.props }/>
<main>
<section>
<div className="clear"></div>
<div className="container">
<h1 className="section_title">Настройки</h1>
<button onClick={ this._handle_onLogout }>Выйти из личного кабинета</button>
</div>
</section>
</main>
<Footer/>
</React.Fragment>
)
}
}
function mapStateToProps(state, ownProps)
{
return {
}
}
export const getServerSideProps = reduxWrapper.getServerSideProps(store =>
async ({ req, res, query }) =>
{
let props = {};
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt === undefined || cookies.jwt === null)
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
else
{
//const tokenValid = await checkToken(cookies.jwt);
const tokenValid = true;
if(!tokenValid)
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
}
}
else
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
return { props: props };
}
);
export default withRouter(connect(mapStateToProps)(IndexPage));

View File

@ -0,0 +1,66 @@
import React from "react";
import Link from "next/link";
const menu = [
{id: 1, name: "Номер телефона", link: "/settings/phone"},
{id: 2, name: "Пароль", link: "/settings/password"},
]
export default class InnerMenu extends React.Component
{
constructor(props)
{
super(props);
this.menuRef = React.createRef();
menu.forEach(item =>
{
this[item.id] = React.createRef();
});
}
componentDidMount()
{
let l = 0;
let m = 0;
for(let i in menu)
{
if(this.props.router.asPath.indexOf(menu[i].link) > -1)
{
m = i;
}
}
for(let i = 0; i < m; i++)
{
l = l + this.menuRef.current.children[i].getBoundingClientRect().width;
}
this.menuRef.current.scrollLeft = l - 50;
}
scrollToCategory = id => {
};
render()
{
return (
<aside>
<ul className="aside_nav" ref={ this.menuRef }>
{ menu.map(item => (
<li key = {item.id} ref={this[item.id]} onClick={() => this.scrollToCategory(item.id)}>
<Link
href={item.link}
shallow
>
<a className={ this.props.router && this.props.router.route === item.link ? "active" : "" }>{item.name}</a>
</Link>
</li>
)) }
</ul>
</aside>
)
}
}

231
pages/settings/password.js Normal file
View File

@ -0,0 +1,231 @@
import React from "react";
import Head from 'next/head';
import Image from 'next/image';
import Link from "next/link";
import cookie from 'cookie';
import { connect } from "react-redux";
import numeral from "numeral";
import { SpinnerCircular } from 'spinners-react';
import { withRouter } from 'next/router';
import { reduxWrapper } from '../../store';
import InnerMenu from "./components/InnerMenu";
import Header from '../components/Header';
import Footer from '../components/Footer';
import Pagination from '../components/Pagination';
import Company from "../components/Company";
import { sendNewPassword } from '../../actions';
class IndexPage extends React.Component
{
constructor(props)
{
super(props);
this.state = {
password_form_step: 1,
password: "",
password_error: false,
new_password: "",
new_password_repeat: "",
new_password_check_loading: false,
new_password_submit_disabled: true,
new_password_error: false,
user: {},
};
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
user: nextProps.user,
};
}
_handle_onPasswordSubmit = (event) =>
{
event.preventDefault();
const { password, new_password, new_password_repeat, new_password_check_loading, user, } = this.state;
if(!new_password_check_loading)
{
this.setState({ new_password_check_loading: true }, () =>
{
sendNewPassword({ email: user.email, password, new_password, new_password_repeat, })
.then((response) =>
{
if(response.status === "success")
{
this.setState({ new_password_error: false, new_password_check_loading: false, password_form_step: 2, });
}
else
{
console.log(response.error);
if(response.status === "error" && response.error === "wrong_email")
{
this.setState({ password_error: true, new_password_error: false, new_password_check_loading: false });
}
}
})
.catch(() =>
{
this.setState({ new_password_error: true, new_password_check_loading: false });
});
});
}
}
_handle_onPasswordChange = (value) =>
{
this.setState({ password: value, password_error: false, new_password_submit_disabled: this._check_fields_disabled([ value, this.state.new_password, this.state.new_password_repeat ]), });
}
_handle_onNewPasswordChange = (value) =>
{
let error = false;
if(this.state.new_password_repeat.length > 0)
{
value === this.state.new_password ? false : true;
error = value.length < 6 ? true : error;
}
this.setState({ new_password: value, new_password_submit_disabled: this._check_fields_disabled([ value, this.state.password, this.state.new_password_repeat ]), new_password_error: false });
}
_handle_onNewPasswordRepeatChange = (value) =>
{
let error = value === this.state.new_password ? false : true;
error = value.length < 6 ? true : error;
this.setState({ new_password_repeat: value, new_password_submit_disabled: error ? true : this._check_fields_disabled([ value, this.state.password, this.state.new_password ]), new_password_error: error });
}
_check_fields_disabled = (values) =>
{
for(let i in values)
{
if(values[i] === "")
{
return true;
}
}
return false;
}
render()
{
console.log("this.state.user", this.state.user);
const { password, password_error, new_password, new_password_repeat, new_password_submit_disabled, new_password_check_loading, new_password_error, password_form_step } = this.state;
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta
name="description"
content="ЛК Эволюция автолизинга"
/>
</Head>
<Header { ...this.props }/>
<main>
<section>
<div className="clear"></div>
<div className="container">
<div className="title_wrapper">
<div className="left">
<h1 className="section_title">Смена пароля</h1>
</div>
<Company/>
</div>
<div className="aside_container about">
<InnerMenu { ...this.props }/>
<article>
{ password_form_step === 1 && (
<div className={`login recovery`}>
<form onSubmit={ this._handle_onPasswordSubmit } className="newPass_form">
<p className="message">Введите Ваш текущий пароль</p>
<div className="form_field">
<input type="password" name="password" value={ password } placeholder="Текущий пароль" onChange={ (event) => this._handle_onPasswordChange(event.target.value) } />
</div>
<p style={{ width: "100%" }}>{ password_error ? 'Ошибка: неверный пароль' : `\u00A0` }</p>
<p style={{ width: "100%" }} className="message"><br/></p>
<p style={{ width: "100%" }} className="message"><br/></p>
<p className="message">Введите новый пароль и повторите новый пароль</p>
<div className="form_field">
<input type="password" name="new_password" value={ new_password } placeholder="Новый пароль" onChange={ (event) => this._handle_onNewPasswordChange(event.target.value) } />
</div>
<div className="form_field">
<input type="password" name="new_password_repeat" value={ new_password_repeat } placeholder="Новый пароль (повторите)" onChange={ (event) => this._handle_onNewPasswordRepeatChange(event.target.value) } />
</div>
<button type="submit" className="button button-blue" disabled={ new_password_submit_disabled }>
{ new_password_check_loading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
) : "Изменить пароль" }
</button>
</form>
<p>{ new_password_error ? 'Внимание: пароли не совпадают' : `\u00A0` }</p>
<p>{ new_password.length < 6 ? 'Внимание: минимальная длина пароля - 6 символов' : `\u00A0` }</p>
</div>
) }
{ password_form_step === 2 && (
<div className={`login recovery`}>
<p className="message">Ваш пароль был успешно изменен.</p>
</div>
) }
</article>
</div>
</div>
</section>
</main>
<Footer/>
</React.Fragment>
)
}
}
function mapStateToProps(state, ownProps)
{
return {
user: state.user,
}
}
export const getServerSideProps = reduxWrapper.getServerSideProps(store =>
async ({ req, res, query }) =>
{
let props = {};
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt === undefined || cookies.jwt === null)
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
else
{
//const tokenValid = await checkToken(cookies.jwt);
const tokenValid = true;
if(!tokenValid)
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
}
}
else
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
return { props: props };
}
);
export default withRouter(connect(mapStateToProps)(IndexPage));

285
pages/settings/phone.js Normal file
View File

@ -0,0 +1,285 @@
import React from "react";
import Head from 'next/head';
import Image from 'next/image';
import Link from "next/link";
import cookie from 'cookie';
import { connect } from "react-redux";
import numeral from "numeral";
import pluralize from 'pluralize-ru';
import { SpinnerCircular } from 'spinners-react';
import { withRouter } from 'next/router';
import { reduxWrapper } from '../../store';
import InnerMenu from "./components/InnerMenu";
import Header from '../components/Header';
import Footer from '../components/Footer';
import Pagination from '../components/Pagination';
import Company from "../components/Company";
import { sendPhoneChangeNumber, sendPhoneChangeNumberSmsCode, setUserPhone } from '../../actions';
class IndexPage extends React.Component
{
constructor(props)
{
super(props);
this.state = {
phone: "",
phone_code: "",
phone_form_step: 1,
phone_number_error: false,
phone_number_format_error: false,
phone_code_error: false,
phone_login_disabled: true,
phone_code_submit_disabled: true,
phone_code_resend_disabled: true,
phone_sms_code_error: false,
timer: 0,
phone_check_loading: false,
code_check_loading: false,
user: {},
};
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
user: nextProps.user,
};
}
_handle_onPhoneSubmit = (event) =>
{
event.preventDefault();
const { user, phone, phone_check_loading } = this.state;
if(!phone_check_loading)
{
this.setState({ phone_check_loading: true }, () =>
{
sendPhoneChangeNumber({ email: user.email, phone })
.then(() =>
{
this.setState({ phone_check_loading: false, phone_number_error: false, timer: 60, phone_form_step: 2, }, () =>
{
this.timer_ref = setInterval(() =>
{
const t = this.state.timer - 1;
this.setState({ timer: t, }, () =>
{
if(t === 0)
{
clearInterval(this.timer_ref);
}
});
}, 1000);
});
})
.catch(() =>
{
this.setState({ phone_number_error: true, phone_check_loading: false });
});
});
}
}
_handle_onCodeSubmit = (event) =>
{
event.preventDefault();
const { phone, phone_code, code_check_loading } = this.state;
if(!code_check_loading)
{
this.setState({ code_check_loading: true }, () =>
{
sendPhoneChangeNumberSmsCode({ phone, code: phone_code })
.then(() =>
{
const new_user = { ...this.state.user };
new_user.phone = phone;
setUserPhone({ dispatch: this.props.dispatch, user: new_user });
this.setState({ phone_sms_code_error: false, code_check_loading: false, phone_form_step: 3 });
})
.catch(() =>
{
this.setState({ phone_sms_code_error: true, code_check_loading: false });
});
});
}
}
_handle_onResendCode = (event) =>
{
this.setState({ phone_sms_code_error: false }, () =>
{
this._handle_onPhoneSubmit(event);
});
}
_handle_onPhoneChange = (value) =>
{
const phone_number_format_error = value.length > 1 && (value[0] !== "+" || value[1] !== "7") ? true : false;
this.setState({ phone: value, phone_login_disabled: this._check_fields_disabled([ value ]), phone_number_error: false, phone_number_format_error: phone_number_format_error });
}
_handle_onPhoneCodeChange = (value) =>
{
this.setState({ phone_code: value, phone_code_submit_disabled: this._check_fields_disabled([ value ]), phone_sms_code_error: false });
}
_check_fields_disabled = (values) =>
{
for(let i in values)
{
if(values[i] === "")
{
return true;
}
}
return false;
}
render()
{
const { user, phone, phone_code, phone_number_error, phone_code_error,
phone_login_disabled, phone_form_step, phone_code_submit_disabled, phone_code_resend_disabled,
timer, phone_sms_code_error, phone_check_loading, code_check_loading, phone_number_format_error } = this.state;
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta
name="description"
content="ЛК Эволюция автолизинга"
/>
</Head>
<Header { ...this.props }/>
<main>
<section>
<div className="clear"></div>
<div className="container">
<div className="title_wrapper">
<div className="left">
<h1 className="section_title">Вход по номеру телефона</h1>
</div>
<Company/>
</div>
<div className="aside_container about">
<InnerMenu { ...this.props }/>
<article>
{ phone_form_step !== 2 && phone_form_step !== 3 && (
<>
{ user.phone !== undefined && user.phone !== null && user.phone !== "" ? (
<p>Номер для авторизации <strong>+{ user.phone.replace(/[^0-9.]/g, '') }</strong>. Вы можете изменить номер телефона для авторизации.</p>
) : (
<p>У Вас отсутствует привязанный номер телефона. Вы можете добавить номер телефона для авторизации.</p>
) }
</>
) }
<p><br/></p>
<div className={`login recovery`}>
{ phone_form_step === 1 && (
<>
<form onSubmit={ this._handle_onPhoneSubmit }>
<div className="form_field">
<input type="text" name="phone" value={ phone } placeholder="Введите номер телефона, например +7 900 111 22 33" onChange={ (event) => this._handle_onPhoneChange(event.target.value) } required={ true }/>
</div>
<button type="submit" className="button button-blue" disabled={ phone_login_disabled }>
{ phone_check_loading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
) : "Получить код" }
</button>
</form>
<p>{ phone_number_error ? 'Ошибка: нет аккаунта с таким номером телефона' : `\u00A0` }</p>
<p>{ phone_number_format_error ? 'Ошибка: номер должен начинаться на +7' : `\u00A0` }</p>
</>
) }
{ phone_form_step === 2 && (
<>
<p className="message">На номер <strong>+{ phone.replace(/[^0-9.]/g, '') }</strong> отправлен код подтверждения.</p>
<form onSubmit={ this._handle_onCodeSubmit }>
<div className="form_field">
<input type="text" name="phone_code" value={ phone_code } placeholder="Введите код из СМС" onChange={ (event) => this._handle_onPhoneCodeChange(event.target.value) } />
</div>
<button type="submit" className="button button-blue" disabled={ phone_code_submit_disabled }>
{ code_check_loading ? (
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
) : "Отправить код" }
</button>
</form>
<div className="resend" style={{ justifyContent: "flex-start" }}>
{ timer !== 0 ? (
<p>Запросить код повторно можно через: { timer } { pluralize(timer, 'секунд', 'секунду', 'секунды', 'секунд') }</p>
) : (
<button className="button button-blue transparent" onClick={ (event) => this._handle_onResendCode(event) }>Запросить код повторно</button>
) }
{/* disabled={ phone_code_resend_disabled }*/}
</div>
<p>{ phone_sms_code_error ? 'Ошибка: Вы указали неверный код' : `\u00A0` }</p>
</>
) }
{ phone_form_step === 3 && (
<p className="message">Вы успешно привязали номер <strong>+{ phone.replace(/[^0-9.]/g, '') }</strong> к Вашей учетной записи.</p>
) }
</div>
</article>
</div>
</div>
</section>
</main>
<Footer/>
</React.Fragment>
)
}
}
function mapStateToProps(state, ownProps)
{
return {
user: state.user,
}
}
export const getServerSideProps = reduxWrapper.getServerSideProps(store =>
async ({ req, res, query }) =>
{
let props = {};
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt === undefined || cookies.jwt === null)
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
else
{
//const tokenValid = await checkToken(cookies.jwt);
const tokenValid = true;
if(!tokenValid)
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
}
}
else
{
res.statusCode = 302;
res.setHeader('Location', `/login`);
}
return { props: props };
}
);
export default withRouter(connect(mapStateToProps)(IndexPage));

View File

@ -97,6 +97,14 @@ const contractReducer = (state = initialState.contract, action) =>
};
}
case actionTypes.CONTRACT_MATERIALS:
{
return {
...state,
materials: action.data.materials,
};
}
default: {
return state;
}

View File

@ -32,6 +32,7 @@ export const defaultState = {
agreement: null,
documents: null,
rules: null,
materials: null,
},
calendar: {
payments: null,