mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-12 08:47:40 -05:00
@@ -1,5 +1,5 @@
|
||||
# build environment
|
||||
FROM node:13.12.0-alpine as build
|
||||
FROM node:19-alpine3.16 as build
|
||||
WORKDIR /app
|
||||
ENV PATH /app/node_modules/.bin:$PATH
|
||||
ARG REACT_APP_BACKEND_SERVER
|
||||
@@ -16,7 +16,7 @@ RUN npm run build
|
||||
RUN env
|
||||
|
||||
# production environment
|
||||
FROM node:13.12.0-alpine
|
||||
FROM node:19-alpine3.16
|
||||
RUN mkdir -p /app/public
|
||||
COPY ./package.json /tmp
|
||||
RUN cd /tmp && npm install
|
||||
|
||||
20140
account/package-lock.json
generated
20140
account/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,18 @@
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@reduxjs/toolkit": "^1.1.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"axios": "0.21.1",
|
||||
"connected-react-router": "6.8.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-scripts": "4.0.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"semantic-ui-css": "2.4.1",
|
||||
"semantic-ui-react": "2.0.1"
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"axios": "1.3.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "6.9.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"redux-thunk": "2.4.2",
|
||||
"semantic-ui-css": "2.5.0",
|
||||
"semantic-ui-react": "2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@@ -40,13 +39,13 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "5.0.0",
|
||||
"connect-mongo": "3.2.0",
|
||||
"express": "4.17.1",
|
||||
"express-session": "1.17.1",
|
||||
"mongoose": "5.11.10",
|
||||
"node-mailjet": "3.3.1",
|
||||
"passport": "0.4.1",
|
||||
"bcrypt": "5.1.0",
|
||||
"connect-mongo": "5.0.0",
|
||||
"express": "4.18.2",
|
||||
"express-session": "1.17.3",
|
||||
"mongoose": "7.0.3",
|
||||
"node-mailjet": "6.0.2",
|
||||
"passport": "0.6.0",
|
||||
"passport-local": "1.0.0",
|
||||
"passport-twitter": "1.0.4"
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@ const path = require("path");
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const bodyParser = require("body-parser");
|
||||
const connectMongo = require("connect-mongo");
|
||||
const MongoStore = require("connect-mongo");
|
||||
const secrets = require("./secrets");
|
||||
|
||||
const MongoStore = connectMongo(session)
|
||||
var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var backend_server = process.env['REACT_APP_BACKEND_SERVER'] != null ? process.env['REACT_APP_BACKEND_SERVER'] : 'https://api.openmhz.com';
|
||||
var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com';
|
||||
var socket_server = process.env['REACT_APP_SOCKET_SERVER'] != null ? process.env['REACT_APP_SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var dev_server = frontend_server + ":3000"
|
||||
var account_dev_server = "http://account.openmhz.test:3000"
|
||||
var admin_dev_server = "http://admin.openmhz.test:3000"
|
||||
|
||||
module.exports = function(app, passport) {
|
||||
app.set("port", 3009)
|
||||
@@ -26,8 +26,6 @@ module.exports = function(app, passport) {
|
||||
app.use(express.static(path.join(process.cwd(), 'public')));
|
||||
|
||||
const sess = {
|
||||
/*saveUninitialized: false,
|
||||
resave: false,*/
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
secret: secrets.sessionSecret,
|
||||
@@ -38,9 +36,12 @@ module.exports = function(app, passport) {
|
||||
secure: false,
|
||||
domain: cookie_domain
|
||||
},
|
||||
store: new MongoStore({
|
||||
url: secrets.db,
|
||||
autoReconnect: true
|
||||
store: MongoStore.create({
|
||||
mongoUrl: secrets.db,
|
||||
autoReconnect: true,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 240,
|
||||
touchAfter: 24 * 3600 // time period in seconds
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,7 +66,8 @@ module.exports = function(app, passport) {
|
||||
allowedOrigins.push(frontend_server);
|
||||
allowedOrigins.push(backend_server);
|
||||
allowedOrigins.push(socket_server);
|
||||
allowedOrigins.push(dev_server);
|
||||
allowedOrigins.push(admin_dev_server);
|
||||
allowedOrigins.push(account_dev_server);
|
||||
var origin = req.headers.origin;
|
||||
|
||||
|
||||
|
||||
@@ -5,34 +5,38 @@
|
||||
|
||||
const LocalStrategy = require("passport-local").Strategy;
|
||||
const User = require("../../models/user");
|
||||
/*
|
||||
By default, LocalStrategy expects to find credentials in parameters named username and password.
|
||||
If your site prefers to name these fields differently, options are available to change the defaults.
|
||||
*/
|
||||
/*
|
||||
By default, LocalStrategy expects to find credentials in parameters named username and password.
|
||||
If your site prefers to name these fields differently, options are available to change the defaults.
|
||||
*/
|
||||
const local = new LocalStrategy({
|
||||
usernameField: "email"
|
||||
}, (email, password, done) => {
|
||||
User.findOne({
|
||||
$or: [{
|
||||
email
|
||||
}, {
|
||||
local: {
|
||||
email
|
||||
}
|
||||
}]
|
||||
}, (err, user) => {
|
||||
if(!user) {
|
||||
console.error("Auth Error - user not found: " + email);
|
||||
return done(null, false, { message: `Invalid email or password` })
|
||||
usernameField: "email"
|
||||
}, async (email, password, done) => {
|
||||
console.log(`${email} ${password}`);
|
||||
const user = await User.findOne({
|
||||
$or: [{
|
||||
email
|
||||
}, {
|
||||
local: {
|
||||
email
|
||||
}
|
||||
}]
|
||||
});
|
||||
if (!user) {
|
||||
console.error("Auth Error - user not found: " + email);
|
||||
return done(null, false, { message: `Invalid email or password` })
|
||||
}
|
||||
if (!user.confirmEmail) {
|
||||
console.error("Auth Error - user has not confirmed email: " + email);
|
||||
return done(null, false, { message: `User's email is not confirmed` })
|
||||
}
|
||||
user.comparePassword(password, (err, isMatch) => {
|
||||
if (isMatch) {
|
||||
return done(null, user)
|
||||
} else {
|
||||
console.error("Auth Error - password mismatch: " + email);
|
||||
return done(null, false, { message: "Invalid email or password" })
|
||||
}
|
||||
user.comparePassword(password, (err, isMatch) => {
|
||||
if (isMatch) {
|
||||
return done(null, user)
|
||||
} else {
|
||||
console.error("Auth Error - password mismatch: " + email);
|
||||
return done(null, false, { message: "Invalid email or password" })
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
module.exports = local;
|
||||
@@ -14,10 +14,13 @@ module.exports = function (app, passport) {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser((id, done) => {
|
||||
User.findById(id, (err, user) => {
|
||||
done(err, user)
|
||||
})
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
const user = await User.findById(id).exec();
|
||||
if (user) {
|
||||
done(null,user);
|
||||
} else {
|
||||
done("User not found", null);
|
||||
}
|
||||
})
|
||||
|
||||
// use the following strategies
|
||||
|
||||
@@ -5,58 +5,52 @@ const System = require("../models/system");
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
exports.getUserSystems = function(_id) {
|
||||
console.log("Listing Systems for: " + _id);
|
||||
System.find(
|
||||
{
|
||||
userId: mongoose.Types.ObjectId(_id)
|
||||
},
|
||||
function(err, systems) {
|
||||
if (err) {
|
||||
console.error("Error - ListSystems: " + err);
|
||||
return({ success: false, message: err });
|
||||
}
|
||||
|
||||
|
||||
var returnSys = systems.map(obj => {
|
||||
var rObj = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
planType,
|
||||
showScreenName
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
planType,
|
||||
showScreenName
|
||||
}))(obj);
|
||||
rObj.id = obj._id;
|
||||
return rObj;
|
||||
});
|
||||
|
||||
|
||||
return({ success: true, systems: returnSys });
|
||||
}
|
||||
);
|
||||
exports.getUserSystems = async function (_id) {
|
||||
console.log("Listing Systems for: " + _id);
|
||||
const userId = new mongoose.Types.ObjectId(req.user._id);
|
||||
const systems = await System.find({ userId: userId });
|
||||
if (systems == null) {
|
||||
return ({ success: false, message: err });
|
||||
}
|
||||
|
||||
var returnSys = systems.map(obj => {
|
||||
var rObj = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
planType,
|
||||
showScreenName
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
planType,
|
||||
showScreenName
|
||||
}))(obj);
|
||||
rObj.id = obj._id;
|
||||
return rObj;
|
||||
});
|
||||
|
||||
|
||||
return ({ success: true, systems: returnSys });
|
||||
}
|
||||
|
||||
exports.listSystems = function(req, res, next) {
|
||||
return listSystems(req.user._id);
|
||||
}
|
||||
|
||||
exports.listSystems = function (req, res, next) {
|
||||
return listSystems(req.user._id);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
const passport = require("passport");
|
||||
const User = require("../models/user");
|
||||
const Mailjet = require('node-mailjet');
|
||||
|
||||
const mailjet = require("node-mailjet").connect(
|
||||
process.env['MAILJET_KEY'],
|
||||
process.env['MAILJET_SECRET']
|
||||
);
|
||||
const crypto = require("crypto");
|
||||
|
||||
var admin_email = process.env['REACT_APP_ADMIN_EMAIL'] != null ? process.env['REACT_APP_ADMIN_EMAIL'] : "luke@openmhz.com";
|
||||
@@ -12,16 +9,25 @@ var site_name = process.env['REACT_APP_SITE_NAME'] != null ? process.env['REACT_
|
||||
var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : "https://account.openmhz.com";
|
||||
var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
|
||||
exports.isLoggedIn = function(req, res, next) {
|
||||
|
||||
const mailjet = new Mailjet({
|
||||
apiKey: process.env['MAILJET_KEY'],
|
||||
apiSecret: process.env['MAILJET_SECRET']
|
||||
});
|
||||
|
||||
|
||||
|
||||
exports.isLoggedIn = function (req, res, next) {
|
||||
if (req.isAuthenticated()) return next();
|
||||
res.redirect("/login");
|
||||
};
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
exports.authenticated = function(req, res, next) {
|
||||
|
||||
exports.authenticated = function (req, res, next) {
|
||||
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
||||
console.log("account/server/controllers/users.js - exports.authenticated() Auth check for user: " + req.user + " is: " + req.isAuthenticated());
|
||||
console.log("account/server/controllers/users.js - exports.authenticated() Auth check for user: " + req.user.email + " is: " + req.isAuthenticated());
|
||||
if (req.isAuthenticated()) {
|
||||
var clientUser = (({
|
||||
firstName,
|
||||
@@ -54,11 +60,13 @@ exports.authenticated = function(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
exports.login = function(req, res, next) {
|
||||
exports.login = function (req, res, next) {
|
||||
// Do email and password validation for the server
|
||||
passport.authenticate("local", function(err, user, info) {
|
||||
|
||||
passport.authenticate("local", function (err, user, info) {
|
||||
if (err) return next(err);
|
||||
if (!user) {
|
||||
console.log("No user");
|
||||
return res.json({
|
||||
success: false,
|
||||
message: info.message
|
||||
@@ -75,6 +83,7 @@ exports.login = function(req, res, next) {
|
||||
req.login(user, loginErr => {
|
||||
|
||||
if (loginErr) {
|
||||
console.log("error")
|
||||
return res.json({
|
||||
success: false,
|
||||
message: loginErr
|
||||
@@ -82,10 +91,14 @@ exports.login = function(req, res, next) {
|
||||
}
|
||||
if (!user.confirmEmail) {
|
||||
req.logout();
|
||||
return res.json({
|
||||
success: false,
|
||||
message: "unconfirmed email",
|
||||
userId: user.id
|
||||
req.logout(function (err) {
|
||||
if (err) { return next(err); }
|
||||
res.clearCookie('sessionId', { domain: cookie_domain, path: '/' });
|
||||
return res.json({
|
||||
success: false,
|
||||
message: "unconfirmed email",
|
||||
userId: user.id
|
||||
});
|
||||
});
|
||||
}
|
||||
//console.log("account/server/controllers/users.js - req.login() Authenicated: " + user.email);
|
||||
@@ -118,199 +131,217 @@ exports.login = function(req, res, next) {
|
||||
})(req, res, next);
|
||||
};
|
||||
|
||||
exports.confirmEmail = function(req, res, next) {
|
||||
exports.confirmEmail = async function (req, res, next) {
|
||||
const userId = req.params["userId"];
|
||||
const token = req.params["token"];
|
||||
|
||||
User.findById(userId, (err, user) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (user.confirmEmail) {
|
||||
console.log("User already confirmed email: " + userId);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "already confirmed email"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const today = new Date();
|
||||
if (user.confirmEmailTTL < today) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "token expired"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (user.confirmEmailToken != token) {
|
||||
console.log(
|
||||
"Token Mismatch DB: " + user.confirmEmailToken + " submitted: " + token
|
||||
);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "token mismatch"
|
||||
});
|
||||
return;
|
||||
}
|
||||
user.confirmEmail = true;
|
||||
user.confirmEmailToken = "";
|
||||
user.save(err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log("User: " + user.email + " confirmed email address");
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
let user = await User.findById(userId).catch(err => {
|
||||
console.error(err);
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.error("User not found: " + userId);
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "User not found"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (user.confirmEmail) {
|
||||
console.log("User already confirmed email: " + userId);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "already confirmed email"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const today = new Date();
|
||||
if (user.confirmEmailTTL < today) {
|
||||
res.status(500);
|
||||
console.error("Expired token for confirming email: " + userId);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "token expired"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (user.confirmEmailToken != token) {
|
||||
console.error(
|
||||
"Token Mismatch DB: " + user.confirmEmailToken + " submitted: " + token
|
||||
);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "token mismatch"
|
||||
});
|
||||
return;
|
||||
}
|
||||
user.confirmEmail = true;
|
||||
user.confirmEmailToken = "";
|
||||
await user.save().catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
console.log("User: " + user.email + " confirmed email address");
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.resetPassword = function(req, res, next) {
|
||||
exports.resetPassword = async function (req, res, next) {
|
||||
const userId = req.params["userId"];
|
||||
const token = req.params["token"];
|
||||
|
||||
User.findById(userId, (err, user) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
if (user.resetPasswordTTL < today) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "token expired"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (user.resetPasswordToken != token) {
|
||||
console.log(
|
||||
"Token Mismatch DB: " + user.resetPasswordToken + " submitted: " + token
|
||||
);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "token mismatch"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
user.password = req.body.password
|
||||
user.confirmEmail = true;
|
||||
user.confirmEmailToken = "";
|
||||
user.resetPasswordToken = "";
|
||||
user.save(err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
let user = await User.findById(userId).catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.error("User not found: " + userId);
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "User not found"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const today = new Date();
|
||||
if (user.resetPasswordTTL < today) {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "token expired"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (user.resetPasswordToken != token) {
|
||||
console.log(
|
||||
"Token Mismatch DB: " + user.resetPasswordToken + " submitted: " + token
|
||||
);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "token mismatch"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
user.password = req.body.password
|
||||
user.confirmEmail = true;
|
||||
user.confirmEmailToken = "";
|
||||
user.resetPasswordToken = "";
|
||||
await user.save().catch(err => {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
// -------------------------------------------
|
||||
exports.sendResetPassword = function(req, res, next) {
|
||||
User.findOne({
|
||||
exports.sendResetPassword = async function (req, res, next) {
|
||||
let user = await User.findOne({
|
||||
email: req.body.email
|
||||
}, (err, user) => {
|
||||
if (err) {
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
if (!user) {
|
||||
console.error("Reset password failed. No user: " + req.body.email);
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "No account register for " + req.body.email
|
||||
});
|
||||
return;
|
||||
}
|
||||
const buffer = crypto.randomBytes(20);
|
||||
const token = buffer.toString("hex");
|
||||
var tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
user.resetPasswordToken = token;
|
||||
user.resetPasswordTTL = tomorrow;
|
||||
await user.save().catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
const request = mailjet.post("send", {
|
||||
version: "v3.1"
|
||||
}).request({
|
||||
Messages: [{
|
||||
From: {
|
||||
Email: admin_email,
|
||||
Name: site_name + " Admin"
|
||||
},
|
||||
To: [{
|
||||
Email: user.email,
|
||||
Name: user.firstName + " " + user.lastName
|
||||
}],
|
||||
Subject: site_name + " - Password Reset",
|
||||
TextPart: "It looks like you may have forgot your password. Copy this link to your browser to reset your password. Let us know if you are receiving this but did not request a password reset. /r" + account_server + "/reset-password/" + user.id + "/" + token,
|
||||
HTMLPart: "<h3>Thanks for using " + site_name + "!</h3><br />It looks like you may have forgot your password. Copy this link to your browser to reset your password. Let us know if you are receiving this but did not request a password reset.<p>" + account_server + "/reset-password/" + user.id + "/" + token + "</p>"
|
||||
}]
|
||||
});
|
||||
request
|
||||
.then(result => {
|
||||
console.log(`Password reset sent to: ${user.email}`)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err.statusCode);
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!user) {
|
||||
console.error("Reset password failed. No user: " + req.body.email);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "No account register for " + req.body.email
|
||||
});
|
||||
return;
|
||||
}
|
||||
const buffer = crypto.randomBytes(20);
|
||||
const token = buffer.toString("hex");
|
||||
var tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
user.resetPasswordToken = token;
|
||||
user.resetPasswordTTL = tomorrow;
|
||||
user.save(err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const request = mailjet.post("send", {
|
||||
version: "v3.1"
|
||||
}).request({
|
||||
Messages: [{
|
||||
From: {
|
||||
Email: admin_email,
|
||||
Name: site_name + " Admin"
|
||||
},
|
||||
To: [{
|
||||
Email: user.email,
|
||||
Name: user.firstName + " " + user.lastName
|
||||
}],
|
||||
Subject: site_name + " - Password Reset",
|
||||
TextPart: "It looks like you may have forgot your password. Copy this link to your browser to reset your password. Let us know if you are receiving this but did not request a password reset. /r" + account_server + "/reset-password/" + user.id + "/" + token,
|
||||
HTMLPart: "<h3>Thanks for using " + site_name + "!</h3><br />It looks like you may have forgot your password. Copy this link to your browser to reset your password. Let us know if you are receiving this but did not request a password reset.<p>" + account_server + "/reset-password/" + user.id + "/" + token + "</p>"
|
||||
}]
|
||||
});
|
||||
request
|
||||
.then(result => {
|
||||
console.log(`Password reset sent to: ${user.email}`)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err.statusCode);
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
});
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
function handleSendConfirmEmail(user) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (user.confirmEmail) {
|
||||
console.log("Error - Send Confirm Email - User already confirmed email: " + user.email);
|
||||
reject({
|
||||
@@ -326,24 +357,23 @@ function handleSendConfirmEmail(user) {
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
user.confirmEmailToken = token;
|
||||
user.confirmEmailTTL = tomorrow;
|
||||
user.save().then(function(user) {
|
||||
return mailjet.post("send", {
|
||||
version: "v3.1"
|
||||
}).request({
|
||||
Messages: [{
|
||||
From: {
|
||||
Email: admin_email,
|
||||
Name: site_name + " Admin"
|
||||
},
|
||||
To: [{
|
||||
Email: user.email,
|
||||
Name: user.firstName + " " + user.lastName
|
||||
}],
|
||||
Subject: "Confirm " + site_name + " Account",
|
||||
TextPart: "Thanks for signing up for " + site_name + ". We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address: " + account_server + "/confirm-email/" + user._id + "/" + token,
|
||||
HTMLPart: "<h3>Thanks for signing up for " + site_name + "</h3><br />We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address:<p> " + account_server + "/confirm-email/" + user._id + "/" + token + "</p>"
|
||||
}]
|
||||
});
|
||||
await user.save();
|
||||
mailjet.post("send", {
|
||||
version: "v3.1"
|
||||
}).request({
|
||||
Messages: [{
|
||||
From: {
|
||||
Email: admin_email,
|
||||
Name: site_name + " Admin"
|
||||
},
|
||||
To: [{
|
||||
Email: user.email,
|
||||
Name: user.firstName + " " + user.lastName
|
||||
}],
|
||||
Subject: "Confirm " + site_name + " Account",
|
||||
TextPart: "Thanks for signing up for " + site_name + ". We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address: " + account_server + "/confirm-email/" + user._id + "/" + token,
|
||||
HTMLPart: "<h3>Thanks for signing up for " + site_name + "</h3><br />We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address:<p> " + account_server + "/confirm-email/" + user._id + "/" + token + "</p>"
|
||||
}]
|
||||
}).then(result => {
|
||||
console.log("Confirm email sent to: " + user.email);
|
||||
resolve({
|
||||
@@ -353,103 +383,110 @@ function handleSendConfirmEmail(user) {
|
||||
}).catch(err => {
|
||||
console.error("Admin Email: " + admin_email + " User Email: " + user.email);
|
||||
console.error("Error - Send Confirm Email - caught: " + err);
|
||||
res.status(500);
|
||||
reject({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
exports.sendConfirmEmail = function(req, res, next) {
|
||||
exports.sendConfirmEmail = async function (req, res, next) {
|
||||
const userId = req.params["userId"];
|
||||
User.findById(userId, (err, user) => {
|
||||
if (err) {
|
||||
console.error("Error - Send Confirm Email: " + err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!user) {
|
||||
console.error("Error - Send Confirm Email: User not found " + userId);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "user not found"
|
||||
});
|
||||
return;
|
||||
}
|
||||
handleSendConfirmEmail(user).then(function(result) {
|
||||
res.json(result);
|
||||
return;
|
||||
}).catch(function(error) {
|
||||
res.json(error);
|
||||
return;
|
||||
let user = await User.findById(userId).catch(err => {
|
||||
|
||||
console.error("Error - Send Confirm Email: " + err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
if (!user) {
|
||||
console.error("Error - Send Confirm Email: User not found " + userId);
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "user not found"
|
||||
});
|
||||
return;
|
||||
}
|
||||
handleSendConfirmEmail(user).then(function (result) {
|
||||
res.json(result);
|
||||
return;
|
||||
}).catch(function (error) {
|
||||
res.json(error);
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
exports.logout = function (req, res, next) {
|
||||
// the logout method is added to the request object automatically by Passport
|
||||
req.logout(function (err) {
|
||||
if (err) { return next(err); }
|
||||
|
||||
res.clearCookie('sessionId', { domain: cookie_domain, path: '/' });
|
||||
return res.json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// -------------------------------------------
|
||||
exports.terms = async function (req, res, next) {
|
||||
const userId = req.params["userId"];
|
||||
let user = await User.findById(userId).catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
exports.logout = function(req, res, next) {
|
||||
// the logout method is added to the request object automatically by Passport
|
||||
req.logout();
|
||||
res.clearCookie('sessionId', {domain: cookie_domain, path:'/'});
|
||||
return res.json({
|
||||
if (req.user.id != userId) {
|
||||
console.log(
|
||||
"Logged in user's ID: " +
|
||||
req.user.id +
|
||||
" does not match Param: " +
|
||||
userId
|
||||
);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "UserID incorrect"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
user.terms = 1.1; //User.termsVer;
|
||||
await user.save().catch(err => {
|
||||
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
};
|
||||
|
||||
exports.terms = function(req, res, next) {
|
||||
const userId = req.params["userId"];
|
||||
User.findById(userId, (err, user) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (req.user.id != userId) {
|
||||
console.log(
|
||||
"Logged in user's ID: " +
|
||||
req.user.id +
|
||||
" does not match Param: " +
|
||||
userId
|
||||
);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "UserID incorrect"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
user.terms = 1.1; //User.termsVer;
|
||||
user.save(err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
});
|
||||
});
|
||||
};
|
||||
return;
|
||||
}
|
||||
// -------------------------------------------
|
||||
|
||||
|
||||
exports.validateProfile = function(req, res, next) {
|
||||
console.log("Validating user profile: "+ req.body.email);
|
||||
exports.validateProfile = function (req, res, next) {
|
||||
console.log("Validating user profile: " + req.body.email);
|
||||
if (!req.body.firstName || (req.body.firstName.length < 2)) {
|
||||
console.error("ERROR: Validate System - req.body.firstName");
|
||||
res.json({
|
||||
@@ -486,68 +523,97 @@ exports.validateProfile = function(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
exports.updateProfile = function(req, res, next) {
|
||||
exports.updateProfile = async function (req, res, next) {
|
||||
const userId = req.params["userId"];
|
||||
|
||||
User.findById(userId, (err, user) => {
|
||||
// is email address already in use?
|
||||
if (req.user.id != userId) {
|
||||
console.log(
|
||||
"ERROR: Logged in user's ID: " +
|
||||
req.user.id +
|
||||
" does not match Param: " +
|
||||
userId
|
||||
);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "UserID incorrect"
|
||||
});
|
||||
return;
|
||||
}
|
||||
// go ahead and create the new user
|
||||
user.firstName = res.locals.firstName
|
||||
user.lastName = res.locals.lastName
|
||||
user.screenName = res.locals.screenName
|
||||
user.location = res.locals.location
|
||||
|
||||
|
||||
user.save(err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
var clientUser = (({
|
||||
firstName,
|
||||
lastName,
|
||||
screenName,
|
||||
location
|
||||
}) => ({
|
||||
firstName,
|
||||
lastName,
|
||||
screenName,
|
||||
location
|
||||
}))(
|
||||
user
|
||||
);
|
||||
console.log("Updated Profile: " + userId)
|
||||
res.json({
|
||||
success: true,
|
||||
user: clientUser
|
||||
});
|
||||
return;
|
||||
user = await User.findById(userId).catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
if (req.user.id != userId) {
|
||||
console.log(
|
||||
"ERROR: Logged in user's ID: " +
|
||||
req.user.id +
|
||||
" does not match Param: " +
|
||||
userId
|
||||
);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "UserID incorrect"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Lets make sure someone else isn't using this screenName
|
||||
screenNameUser = await User.findOne({ screenName: req.body.screenName }).catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
// Did we find a user with the screeName, is it not us?
|
||||
if (screenNameUser && (screenNameUser.userId != user.userId)) {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Screen Name already in use"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// go ahead and create the new user
|
||||
user.firstName = res.locals.firstName
|
||||
user.lastName = res.locals.lastName
|
||||
user.screenName = res.locals.screenName
|
||||
user.location = res.locals.location
|
||||
|
||||
|
||||
await user.save().catch(err => {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
var clientUser = (({
|
||||
firstName,
|
||||
lastName,
|
||||
screenName,
|
||||
location
|
||||
}) => ({
|
||||
firstName,
|
||||
lastName,
|
||||
screenName,
|
||||
location
|
||||
}))(
|
||||
user
|
||||
);
|
||||
console.log("Updated Profile: " + userId)
|
||||
res.json({
|
||||
success: true,
|
||||
user: clientUser
|
||||
});
|
||||
return;
|
||||
};
|
||||
// -------------------------------------------
|
||||
|
||||
exports.register = function(req, res, next) {
|
||||
exports.register = async function (req, res, next) {
|
||||
console.log("Registration request for: " + req.body.email);
|
||||
|
||||
User.findOne({
|
||||
|
||||
let user = await User.findOne({
|
||||
$or: [{
|
||||
email: req.body.email
|
||||
}, {
|
||||
@@ -555,49 +621,79 @@ exports.register = function(req, res, next) {
|
||||
email: req.body.email
|
||||
}
|
||||
}]
|
||||
}, (err, user) => {
|
||||
// is email address already in use?
|
||||
if (user) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Email already in use"
|
||||
});
|
||||
return;
|
||||
}
|
||||
// go ahead and create the new user
|
||||
var user = (({
|
||||
firstName,
|
||||
lastName,
|
||||
screenName,
|
||||
location,
|
||||
email,
|
||||
password
|
||||
}) => ({
|
||||
firstName,
|
||||
lastName,
|
||||
screenName,
|
||||
location,
|
||||
|
||||
}))(
|
||||
res.locals
|
||||
);
|
||||
user.password = req.body.password;
|
||||
user.email = req.body.email;
|
||||
|
||||
User.create(user).then(function(dbUser) {
|
||||
console.log("Successfully registered: " + dbUser.email + ", now sending confirmation email.");
|
||||
// Since regiestration worked, send a confirmation email.
|
||||
return handleSendConfirmEmail(dbUser);
|
||||
}).then(function(result) {
|
||||
res.json(result);
|
||||
return;
|
||||
}).catch(function(err) {
|
||||
console.error("Error creating user: " + user.email + " Error: " +err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
};
|
||||
// is email address already in use?
|
||||
if (user) {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Email already in use"
|
||||
});
|
||||
return;
|
||||
}
|
||||
user = await User.findOne({ screenName: req.body.screenName }).catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
// is email address already in use?
|
||||
if (user) {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Screen Name already in use"
|
||||
});
|
||||
return;
|
||||
}
|
||||
// go ahead and create the new user
|
||||
user = (({
|
||||
firstName,
|
||||
lastName,
|
||||
screenName,
|
||||
location,
|
||||
email,
|
||||
password
|
||||
}) => ({
|
||||
firstName,
|
||||
lastName,
|
||||
screenName,
|
||||
location,
|
||||
|
||||
}))(
|
||||
res.locals
|
||||
);
|
||||
user.password = req.body.password;
|
||||
user.email = req.body.email;
|
||||
|
||||
let savedUser = await User.create(user)
|
||||
|
||||
console.log("Successfully registered: " + user.email + ", now sending confirmation email.");
|
||||
// Since registration worked, send a confirmation email.
|
||||
handleSendConfirmEmail(savedUser).then(function (result) {
|
||||
console.log("Confirmation email sent to: " + user.email);
|
||||
res.json({
|
||||
success: true,
|
||||
message: result
|
||||
});
|
||||
return;
|
||||
}).catch(function (err) {
|
||||
console.error("Error creating user: " + user.email + " Error: " + err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
}
|
||||
@@ -15,39 +15,39 @@ const app = express()
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
async function connect() {
|
||||
const connect = async () => {
|
||||
|
||||
// Demonstrate the readyState and on event emitters
|
||||
console.log(mongoose.connection.readyState); //logs 0
|
||||
mongoose.connection.on('connecting', () => {
|
||||
console.log('Mongoose is connecting')
|
||||
console.log(mongoose.connection.readyState); //logs 2
|
||||
mongoose.connection.on('connecting', () => {
|
||||
console.log('Mongoose is connecting')
|
||||
console.log(mongoose.connection.readyState); //logs 2
|
||||
});
|
||||
mongoose.connection.on('connected', () => {
|
||||
console.log('Mongoose is connected');
|
||||
console.log(mongoose.connection.readyState); //logs 1
|
||||
console.log('Mongoose is connected');
|
||||
console.log(mongoose.connection.readyState); //logs 1
|
||||
});
|
||||
mongoose.connection.on('disconnecting', () => {
|
||||
console.log('Mongoose is disconnecting');
|
||||
console.log(mongoose.connection.readyState); // logs 3
|
||||
console.log('Mongoose is disconnecting');
|
||||
console.log(mongoose.connection.readyState); // logs 3
|
||||
});
|
||||
|
||||
// Connect to a MongoDB server running on 'localhost:27017' and use the
|
||||
// 'test' database.
|
||||
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true }, (err, res) => {
|
||||
if (err) {
|
||||
console.log(`Mongoose - Error connecting to ${secrets.db}. ${err}`)
|
||||
} else {
|
||||
console.log(`Mongoose Successfully connected to ${secrets.db}.`)
|
||||
}
|
||||
})
|
||||
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true }).catch(err => {
|
||||
|
||||
console.error(`Mongoose - Error connecting to ${secrets.db}. ${err}`)
|
||||
|
||||
});
|
||||
console.log("All Done");
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
mongoose.connection.on('error', err => {
|
||||
console.error("Mongoose Error!")
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
mongoose.connection.on('disconnected', () => {
|
||||
console.log('Mongoose disconnected');
|
||||
console.log(mongoose.connection.readyState); //logs 0
|
||||
@@ -72,16 +72,16 @@ configurePassport(app, passport)
|
||||
configureExpress(app, passport)
|
||||
|
||||
|
||||
app.all('*', function(req, res, next) {
|
||||
var allowedOrigins = [account_server, admin_server, frontend_server];
|
||||
var origin = req.headers.origin;
|
||||
if(allowedOrigins.indexOf(origin) > -1){
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
res.header("Access-Control-Allow-Credentials", "true");
|
||||
res.header("Access-Control-Allow-Headers", "X-Requested-With");
|
||||
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
||||
next();
|
||||
app.all('*', function (req, res, next) {
|
||||
var allowedOrigins = [account_server, admin_server, frontend_server];
|
||||
var origin = req.headers.origin;
|
||||
if (allowedOrigins.indexOf(origin) > -1) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
res.header("Access-Control-Allow-Credentials", "true");
|
||||
res.header("Access-Control-Allow-Headers", "X-Requested-With");
|
||||
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
||||
next();
|
||||
});
|
||||
|
||||
// -------------------------------------------
|
||||
@@ -94,8 +94,8 @@ app.post("/users/:userId/reset-password/:token", users.resetPassword)
|
||||
app.post("/api/send-reset-password", users.sendResetPassword)
|
||||
app.post("/users/:userId", users.isLoggedIn, users.validateProfile, users.updateProfile)
|
||||
app.post("/users/:userId/terms", users.isLoggedIn, users.terms)
|
||||
app.post("/users/:userId/send-confirm", users.sendConfirmEmail)
|
||||
app.post("/users/:userId/confirm/:token", users.confirmEmail)
|
||||
app.post("/users/:userId/send-confirm", users.sendConfirmEmail)
|
||||
app.post("/users/:userId/confirm/:token", users.confirmEmail)
|
||||
|
||||
app.get("*", (req, res, next) => {
|
||||
res.sendFile(__dirname + '/public/index.html');
|
||||
|
||||
@@ -1,15 +1,38 @@
|
||||
import React, { Component } from "react"
|
||||
import NavigationContainer from "./NavigationContainer"
|
||||
import React from "react"
|
||||
import Navigation from "./Navigation"
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return(
|
||||
<div>
|
||||
<NavigationContainer />
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
import Restricted from "./Restricted"
|
||||
|
||||
import Login from "../User/Login"
|
||||
import Register from "../User/Register"
|
||||
import Profile from "../User/Profile"
|
||||
import ConfirmEmail from "../User/ConfirmEmail"
|
||||
import WaitConfirmEmail from "../User/WaitConfirmEmail"
|
||||
import SentConfirmEmail from "../User/SentConfirmEmail"
|
||||
import SendResetPassword from "../User/SendResetPassword"
|
||||
import ResetPassword from "../User/ResetPassword"
|
||||
import Terms from "../User/Terms"
|
||||
|
||||
const App = (params) => {
|
||||
return (
|
||||
<div>
|
||||
<Navigation />
|
||||
<Routes>
|
||||
<Route exact path="/" element={<Restricted><Profile /></Restricted>} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/wait-confirm-email" element={<WaitConfirmEmail />} />
|
||||
<Route path="/sent-confirm-email" element={<SentConfirmEmail />} />
|
||||
<Route path="/send-reset-password" element={<SendResetPassword />} />
|
||||
<Route path="/reset-password/:userId/:token" element={<ResetPassword />} />
|
||||
<Route path="/confirm-email/:userId/:token" element={<ConfirmEmail />} />
|
||||
<Route path="/terms" element={<Restricted><Terms /></Restricted>} />
|
||||
<Route path="/profile" element={<Restricted><Profile /></Restricted>} />
|
||||
<Route render={() => (<div>Miss</div>)} />
|
||||
</Routes>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default App
|
||||
|
||||
@@ -1,53 +1,39 @@
|
||||
import React, { Component } from "react";
|
||||
import { Menu, Dropdown } from "semantic-ui-react";
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { logoutUser } from "../features/user/userSlice";
|
||||
|
||||
const navStyle = {
|
||||
marginBottom: "30px"
|
||||
};
|
||||
|
||||
class Navigation extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._logout = this._logout.bind(this);
|
||||
}
|
||||
const Navigation = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
const { email } = useSelector((state) => state.user);
|
||||
|
||||
_logout = event => {
|
||||
const logout = event => {
|
||||
event.preventDefault();
|
||||
this.props.manualLogout();
|
||||
dispatch(logoutUser({}));
|
||||
};
|
||||
|
||||
render() {
|
||||
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
|
||||
return (
|
||||
<div>
|
||||
{this.props.user.authenticated ? (
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
{process.env.REACT_APP_SITE_NAME}
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
{/* <Link to="/update-plans">
|
||||
<Menu.Item name="systems">Update Plans</Menu.Item>
|
||||
</Link> */}
|
||||
<Dropdown item text={this.props.user.email}>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
|
||||
{/* <Dropdown.Item href={billingLink}>Billing</Dropdown.Item> */}
|
||||
<Dropdown.Item onClick={this._logout}>Logout</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Menu.Menu>
|
||||
</Menu>
|
||||
) : (
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
OpenMHz
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
|
||||
return (
|
||||
<div>
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
{process.env.REACT_APP_SITE_NAME}
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
<Dropdown item text={ email }>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
|
||||
<Dropdown.Item onClick={logout}>Logout</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Menu.Menu>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Navigation;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as userActions from "../User/user-actions"
|
||||
import Navigation from "./Navigation"
|
||||
|
||||
|
||||
// Function passed in to `connect` to subscribe to Redux store updates.
|
||||
// Any time it updates, mapStateToProps is called.
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Connects React component to the redux store
|
||||
// It does not modify the component class passed to it
|
||||
// Instead, it returns a new, connected component class, for you to use.
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(Navigation)
|
||||
@@ -1,55 +1,31 @@
|
||||
// in src/restricted.js
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import * as types from "../User/user-constants";
|
||||
import axios from "axios";
|
||||
import React, { useEffect } from "react";
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { authenticateUser } from "../features/user/userSlice";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
/**
|
||||
* Higher-order component (HOC) to wrap restricted pages
|
||||
*/
|
||||
const restricted = (BaseComponent, store) => {
|
||||
class Restricted extends Component {
|
||||
componentWillMount() {
|
||||
this.checkAuthentication(this.props);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.location !== this.props.location) {
|
||||
this.checkAuthentication(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
checkAuthentication(params) {
|
||||
const { history } = params;
|
||||
const authenticated = store.getState().user.authenticated;
|
||||
if (!authenticated) {
|
||||
axios
|
||||
.get("/authenticated")
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const user = response.data.user
|
||||
store.dispatch({
|
||||
type: types.LOGIN_SUCCESS_USER,
|
||||
data: user
|
||||
});
|
||||
} else {
|
||||
history.replace({ pathname: '/login',
|
||||
state: { nextPathname: params.location.pathname } });
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened during logout that triggered an Error
|
||||
history.replace({ pathname: '/login',
|
||||
state: { nextPathname: params.location.pathname } });
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
const Restricted = ({ children }) => {
|
||||
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return <BaseComponent {...this.props} />;
|
||||
}
|
||||
useEffect(() => {
|
||||
dispatch(authenticateUser({}));
|
||||
}, []);
|
||||
|
||||
if (hasAuthenticated) {
|
||||
if (authenticated) {
|
||||
return children;
|
||||
}
|
||||
return withRouter(Restricted);
|
||||
}
|
||||
export default restricted
|
||||
else {
|
||||
navigate("/login")
|
||||
}
|
||||
}
|
||||
return <div />
|
||||
|
||||
};
|
||||
|
||||
export default Restricted
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Container,
|
||||
Dimmer,
|
||||
@@ -7,72 +7,65 @@ import {
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { confirmEmail } from "../features/user/userSlice";
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
class ConfirmEmail extends Component {
|
||||
const ConfirmEmail = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [confirmMessage, setConfirmMessage] = useState({ success: false, message: "" });
|
||||
const { userId, token } = useParams();
|
||||
|
||||
state = {
|
||||
loading: true,
|
||||
confirmMessage: { success: false, message:""}
|
||||
};
|
||||
componentDidMount() {
|
||||
const { match: { params } } = this.props;
|
||||
|
||||
this.props
|
||||
.confirmEmail(params.userId, params.token)
|
||||
.then(confirmMessage => {
|
||||
if (confirmMessage) {
|
||||
// report to the user is there was a problem during login
|
||||
this.setState({
|
||||
confirmMessage: confirmMessage, loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
const checkConfirmation = async(userId, token) => {
|
||||
const result = await dispatch(confirmEmail({ userId, token })).unwrap();
|
||||
if (result) {
|
||||
setConfirmMessage(result);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
var dimmerProps = { };
|
||||
if (this.state.loading) {
|
||||
dimmerProps["active"] = true;
|
||||
}
|
||||
return (
|
||||
useEffect(() => {
|
||||
checkConfirmation(userId, token);
|
||||
}, [userId, token]);
|
||||
|
||||
<Container text>
|
||||
const dimmerProps = { active: loading };
|
||||
|
||||
return (
|
||||
<Container text>
|
||||
<Dimmer {...dimmerProps}>
|
||||
<Loader indeterminate>Confirming Email Address</Loader>
|
||||
</Dimmer>
|
||||
|
||||
|
||||
{this.state.confirmMessage.success ? (
|
||||
<Message icon>
|
||||
|
||||
<Icon name='check'/>
|
||||
<Message.Content>
|
||||
<Message.Header>Success</Message.Header>
|
||||
<p>You have successfully confirmed you email address.</p>
|
||||
<Link to="/">
|
||||
<Button
|
||||
size="large"
|
||||
content="Continue to Login"
|
||||
fluid
|
||||
/></Link>
|
||||
<Loader indeterminate>Confirming Email Address</Loader>
|
||||
</Dimmer>
|
||||
|
||||
</Message.Content>
|
||||
</Message>
|
||||
) : (
|
||||
<Message icon>
|
||||
<Icon name='exclamation'/>
|
||||
{confirmMessage.success ? (
|
||||
<Message icon>
|
||||
<Icon name='check' />
|
||||
<Message.Content>
|
||||
<Message.Header>Success</Message.Header>
|
||||
<p>You have successfully confirmed you email address.</p>
|
||||
<Link to="/login">
|
||||
<Button
|
||||
size="large"
|
||||
content="Continue to Login"
|
||||
fluid
|
||||
/></Link>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
) : (
|
||||
<Message icon>
|
||||
<Icon name='exclamation' />
|
||||
<Message.Content>
|
||||
<Message.Header>Error</Message.Header>
|
||||
<p>{this.state.confirmMessage.message}</p>
|
||||
<p>{confirmMessage.message}</p>
|
||||
</Message.Content>
|
||||
|
||||
</Message>
|
||||
)}
|
||||
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
</Message>
|
||||
)}
|
||||
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConfirmEmail;
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import ConfirmEmail from "./ConfirmEmail"
|
||||
import * as userActions from "./user-actions"
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(ConfirmEmail)
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
|
||||
import { loginUser } from "../features/user/userSlice";
|
||||
import { useDispatch } from 'react-redux'
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
@@ -26,74 +28,54 @@ const forgotStyle = {
|
||||
// ----------------------------------------------------
|
||||
|
||||
|
||||
class Login extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._onLoginSubmit = this._onLoginSubmit.bind(this);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleRegisterClick = this.handleRegisterClick.bind(this);
|
||||
}
|
||||
const Login = (props) => {
|
||||
const [loginMessage, setLoginMessage] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const navigate = useNavigate();
|
||||
let [searchParams] = useSearchParams();
|
||||
const nextLocation = searchParams.get("nextLocation");
|
||||
const dispatch = useDispatch();
|
||||
|
||||
state = {
|
||||
loginMessage: "",
|
||||
email: "",
|
||||
password: ""
|
||||
};
|
||||
handleRegisterClick = () => {
|
||||
this.props.changeUrl("/register");
|
||||
};
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
_onLoginSubmit(event) {
|
||||
const loginSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
const { email, password } = this.state;
|
||||
var success = { type: "path", nextPathname: "/" };
|
||||
if (this.props.nextPathname) {
|
||||
success.nextPathname = this.props.nextPathname;
|
||||
}
|
||||
if (this.props.nextLocation) {
|
||||
success.type = "location";
|
||||
switch (this.props.nextLocation) {
|
||||
case "frontend":
|
||||
success.nextLocation = process.env.REACT_APP_FRONTEND_SERVER;
|
||||
break;
|
||||
default:
|
||||
case "admin":
|
||||
success.nextLocation = process.env.REACT_APP_ADMIN_SERVER;
|
||||
break;
|
||||
const result = await dispatch(loginUser({email,password})).unwrap();
|
||||
if (result.success) {
|
||||
|
||||
if (nextLocation) {
|
||||
switch (nextLocation) {
|
||||
case "frontend":
|
||||
window.location = process.env.REACT_APP_FRONTEND_SERVER;
|
||||
break;
|
||||
default:
|
||||
case "admin":
|
||||
window.location = process.env.REACT_APP_ADMIN_SERVER;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
console.log(result)
|
||||
} else {
|
||||
console.error(result);
|
||||
setLoginMessage(result.message);
|
||||
if (result.message === "unconfirmed email") {
|
||||
/*data.userId = response.data.userId;
|
||||
dispatch(loginEmailError(data));*/
|
||||
navigate("/wait-confirm-email");
|
||||
}
|
||||
}
|
||||
|
||||
// Passed in via react-redux. Returns a promise.
|
||||
this.props
|
||||
.manualLogin(
|
||||
{
|
||||
// this function is passed in via react-redux
|
||||
email,
|
||||
password
|
||||
},
|
||||
success
|
||||
|
||||
) // holds the path to redirect to after login (if any)
|
||||
.then(loginMessage => {
|
||||
if (loginMessage) {
|
||||
// report to the user is there was a problem during login
|
||||
this.setState({
|
||||
loginMessage
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
var loginMessage = "";
|
||||
let loginMessageDisplay =(<></>)
|
||||
|
||||
if (this.state.loginMessage) {
|
||||
loginMessage = (
|
||||
if (loginMessage) {
|
||||
loginMessageDisplay = (
|
||||
<Message icon>
|
||||
<Icon name="lemon" />
|
||||
<Message.Content>
|
||||
<Message.Header>Problems...</Message.Header>
|
||||
{this.state.loginMessage}
|
||||
{loginMessage}
|
||||
</Message.Content>
|
||||
</Message>
|
||||
);
|
||||
@@ -147,12 +129,12 @@ class Login extends Component {
|
||||
<Button
|
||||
size="large"
|
||||
content="Register"
|
||||
onClick={this.handleRegisterClick}
|
||||
onClick={()=>navigate("/register")}
|
||||
fluid
|
||||
/>
|
||||
<Divider style={dividerStyle} horizontal>Or</Divider>
|
||||
<Header as="h3">Login</Header>
|
||||
<Form onSubmit={this._onLoginSubmit}>
|
||||
<Form onSubmit={loginSubmit}>
|
||||
<Segment padded>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
@@ -160,7 +142,8 @@ class Login extends Component {
|
||||
iconPosition="left"
|
||||
type="text"
|
||||
name="email"
|
||||
onChange={this.handleInputChange}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
value={email}
|
||||
placeholder="E-mail address"
|
||||
/>
|
||||
</Form.Field>
|
||||
@@ -170,7 +153,8 @@ class Login extends Component {
|
||||
iconPosition="left"
|
||||
type="password"
|
||||
name="password"
|
||||
onChange={this.handleInputChange}
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
placeholder="Password"
|
||||
/>
|
||||
</Form.Field>
|
||||
@@ -178,7 +162,7 @@ class Login extends Component {
|
||||
<Button type="submit" size="large" value="Login" fluid>
|
||||
Login
|
||||
</Button>
|
||||
{loginMessage}
|
||||
{loginMessageDisplay}
|
||||
</Segment>
|
||||
</Form>
|
||||
<div style={forgotStyle} >
|
||||
@@ -190,6 +174,6 @@ class Login extends Component {
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default Login;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as userActions from "./user-actions"
|
||||
import Login from "./Login"
|
||||
import queryString from '../query-string';
|
||||
|
||||
|
||||
// Function passed in to `connect` to subscribe to Redux store updates.
|
||||
// Any time it updates, mapStateToProps is called.
|
||||
// The second argument "ownProps" contains props passed to the component
|
||||
const mapStateToProps = (state, props) => {
|
||||
|
||||
let nextPathname = false;
|
||||
|
||||
try {nextPathname = props.location.state.nextPathname}
|
||||
catch(err) {}
|
||||
const uri = queryString.parse(props.location.search);
|
||||
let nextLocation = uri['nextLocation'];
|
||||
|
||||
return {
|
||||
|
||||
user: state.user,
|
||||
nextPathname, // this prop passed in by React Router
|
||||
nextLocation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Connects React component to the redux store
|
||||
// It does not modify the component class passed to it
|
||||
// Instead, it returns a new, connected component class, for you to use.
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(Login)
|
||||
@@ -1,45 +1,36 @@
|
||||
import React, { Component } from "react"
|
||||
import { useState, } from "react";
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import UserForm from "./UserForm";
|
||||
import {
|
||||
Container,
|
||||
Header
|
||||
} from "semantic-ui-react";
|
||||
import { updateProfile } from "../features/user/userSlice";
|
||||
|
||||
class Profile extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleProfileSubmit = this.handleProfileSubmit.bind(this);
|
||||
}
|
||||
const Profile = (props) => {
|
||||
const [message, setMessage] = useState("");
|
||||
const user = useSelector((state) => state.user);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleProfileSubmit = async (updatedUser) => {
|
||||
updatedUser.userId = user.userId
|
||||
|
||||
state = {
|
||||
requestMessage: ""
|
||||
const result = await dispatch(updateProfile(updatedUser)).unwrap();
|
||||
if (result.success) {
|
||||
console.log(result)
|
||||
} else {
|
||||
setMessage(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
handleProfileSubmit(user) {
|
||||
user.userId = this.props.user.userId
|
||||
this.props
|
||||
.updateProfile(user)
|
||||
.then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
requestMessage
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// The Key on UserForm get updated when the user is loaded. This forces the Component to be completely redrawn, otherwise the changes in the Prop are not copied to State.
|
||||
return(
|
||||
<Container text>
|
||||
<Header as="h1">Update Profile</Header>
|
||||
<UserForm onSubmit={this.handleProfileSubmit} user={this.props.user} isEditing={true} requestMessage={this.state.requestMessage} isWaiting={this.props.isWaiting} key={this.props.user.userId}/>
|
||||
<UserForm onSubmit={handleProfileSubmit} user={user} isEditing={true} requestMessage={message} isWaiting={props.isWaiting} key={user.userId}/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Profile
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import Profile from "./Profile"
|
||||
import * as userActions from "./user-actions"
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user,
|
||||
isWaiting: state.user.isWaiting
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(Profile)
|
||||
@@ -1,4 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import { useState } from "react";
|
||||
import { registerUser } from "../features/user/userSlice";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux'
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
@@ -8,53 +11,39 @@ import {
|
||||
|
||||
import UserForm from "./UserForm";
|
||||
|
||||
// ----------------------------------------------------
|
||||
class Register extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._onRegisterSubmit = this._onRegisterSubmit.bind(this);
|
||||
}
|
||||
function Register(props) {
|
||||
const [requestMessage, setRequestMessage] = useState("");
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const onRegisterSubmit = async (user) => {
|
||||
const result = await dispatch(registerUser(user)).unwrap();
|
||||
if (result.success) {
|
||||
navigate("/sent-confirm-email");
|
||||
} else {
|
||||
console.error(result);
|
||||
setRequestMessage(result.message);
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
requestMessage: ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
_onRegisterSubmit(user) {
|
||||
this.props
|
||||
.manualRegister(user)
|
||||
.then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
requestMessage
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
<Message icon>
|
||||
<Icon name='trophy'/>
|
||||
<Message.Content>
|
||||
<Message.Header>Welcome!</Message.Header>
|
||||
<p>Creating an account on OpenMHz is the first step in sharing radio system recordings using Trunk Recorder.</p>
|
||||
<p>After creating an account, head over <a href="https://github.com/robotastic/trunk-recorder">here</a> to learn more about Trunk Recorder</p>
|
||||
<p> - Luke</p>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
</Container>
|
||||
<Container text>
|
||||
<Header style={{ paddingTop: '20px' }} as="h1">Register</Header>
|
||||
<UserForm onSubmit={this._onRegisterSubmit} requestMessage={this.state.requestMessage} isWaiting={this.props.isWaiting}/>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<Icon name='trophy' />
|
||||
<Message.Content>
|
||||
<Message.Header>Welcome!</Message.Header>
|
||||
<p>Creating an account on OpenMHz is the first step in sharing radio system recordings using Trunk Recorder.</p>
|
||||
<p>After creating an account, head over <a href="https://github.com/robotastic/trunk-recorder">here</a> to learn more about Trunk Recorder</p>
|
||||
<p> - Luke</p>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
</Container>
|
||||
<Container text>
|
||||
<Header style={{ paddingTop: '20px' }} as="h1">Register</Header>
|
||||
<UserForm onSubmit={onRegisterSubmit} requestMessage={requestMessage} isWaiting={props.isWaiting} />
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Register;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as userActions from "./user-actions"
|
||||
import Register from "./Register"
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
isWaiting: state.user.isWaiting
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(Register)
|
||||
@@ -1,4 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { resetPassword } from "../features/user/userSlice";
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux'
|
||||
import {
|
||||
Container,
|
||||
Form,
|
||||
@@ -6,101 +9,100 @@ import {
|
||||
Button,
|
||||
} from "semantic-ui-react";
|
||||
|
||||
class ResetPassword extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleResetPassword = this.handleResetPassword.bind(this);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
}
|
||||
state = {
|
||||
checkInputMessages: [],
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
passwordError: false,
|
||||
confirmPasswordError: false,
|
||||
sentReset: false
|
||||
};
|
||||
const ResetPassword = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
const [confirmPasswordError, setConfirmPasswordError] = useState(false);
|
||||
const [checkInputMessages, setCheckInputMessages] = useState([]);
|
||||
const { userId, token } = useParams();
|
||||
|
||||
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
|
||||
handleResetPassword(event) {
|
||||
const handleResetPassword = async () => {
|
||||
let error = false;
|
||||
var checkInputMessages = [];
|
||||
var inputMessages = [];
|
||||
|
||||
if (this.state.password === "") {
|
||||
this.setState({ passwordError: true });
|
||||
checkInputMessages.push("Password is required");
|
||||
if (password === "") {
|
||||
setPasswordError(true);
|
||||
inputMessages.push("Password is required");
|
||||
error = true;
|
||||
} if (this.state.password.length < 7) {
|
||||
this.setState({ passwordError: true });
|
||||
checkInputMessages.push("Password must be 7 charecters or more");
|
||||
error = true;
|
||||
}else {
|
||||
this.setState({ passwordError: false });
|
||||
}
|
||||
|
||||
if (this.state.password !== this.state.confirmPassword) {
|
||||
this.setState({ confirmPasswordError: true });
|
||||
checkInputMessages.push("The passwords did not match");
|
||||
} else if (password.length < 7) {
|
||||
setPasswordError(true);
|
||||
inputMessages.push("Password must be 7 characters or more");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ confirmPasswordError: false });
|
||||
setPasswordError(false);
|
||||
}
|
||||
|
||||
const { match: { params } } = this.props;
|
||||
this.setState({ checkInputMessages: checkInputMessages });
|
||||
if (password !== confirmPassword) {
|
||||
setConfirmPasswordError(true);
|
||||
inputMessages.push("The passwords did not match");
|
||||
error = true;
|
||||
} else {
|
||||
setConfirmPasswordError(false);
|
||||
}
|
||||
|
||||
setCheckInputMessages(inputMessages);
|
||||
if (!error) {
|
||||
this.props.resetPassword(params.userId, params.token, this.state.password).then(resetSent => {
|
||||
this.setState({resetSent: true});
|
||||
});
|
||||
const result = await dispatch(resetPassword({ userId, token, password })).unwrap();
|
||||
if (result.success) {
|
||||
navigate("/login");
|
||||
} else {
|
||||
console.error(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
<Segment>
|
||||
<Form
|
||||
className="raised padding segment"
|
||||
onSubmit={this.handleSubmit}
|
||||
>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
name="password"
|
||||
type="password"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.passwordError}
|
||||
label="Password"
|
||||
placeholder="Password..."
|
||||
<Form className="raised padding segment">
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
name="password"
|
||||
type="password"
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
error={passwordError}
|
||||
label="Password"
|
||||
placeholder="Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
onChange={e => setConfirmPassword(e.target.value)}
|
||||
error={confirmPasswordError}
|
||||
label="Confirm Password"
|
||||
placeholder="Confirm Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
<Button
|
||||
size="large"
|
||||
content="Reset Password"
|
||||
onClick={handleResetPassword}
|
||||
fluid
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.confirmPasswordError}
|
||||
label="Confirm Password"
|
||||
placeholder="Confirm Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
<Button
|
||||
size="large"
|
||||
content="Reset Password"
|
||||
onClick={this.handleResetPassword}
|
||||
fluid
|
||||
/>
|
||||
</Form>
|
||||
</Segment>
|
||||
{checkInputMessages.length > 0 && (
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
{checkInputMessages.map((msg) => (
|
||||
<p key={msg} style={{ color: "red" }}>
|
||||
{msg}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</Segment>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPassword;
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import ResetPassword from "./ResetPassword"
|
||||
import * as userActions from "./user-actions"
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(ResetPassword)
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Form,
|
||||
Grid,
|
||||
@@ -7,106 +7,97 @@ import {
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
import { sendResetPassword } from "../features/user/userSlice";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
const returnStyle = {
|
||||
marginTop: "30px"
|
||||
};
|
||||
|
||||
class SendResetPassword extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSendResetPassword = this.handleSendResetPassword.bind(this);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
}
|
||||
state = {
|
||||
checkInputMessages: "",
|
||||
email: "",
|
||||
emailError: false,
|
||||
sentReset: false
|
||||
const SendResetPassword = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [checkInputMessages, setCheckInputMessages] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [emailError, setEmailError] = useState(false);
|
||||
const [resetSent, setResetSent] = useState(false);
|
||||
|
||||
const returnStyle = {
|
||||
marginTop: "30px"
|
||||
};
|
||||
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
|
||||
handleSendResetPassword(event) {
|
||||
|
||||
|
||||
if (this.state.email === "") {
|
||||
this.setState({ emailError: true, checkInputMessages: "Email is required"});
|
||||
|
||||
const handleSendResetPassword = async () => {
|
||||
if (email === "") {
|
||||
setEmailError(true);
|
||||
setCheckInputMessages("Email is required");
|
||||
} else {
|
||||
this.setState({ emailError: false });
|
||||
|
||||
this.props.sendResetPassword({email: this.state.email}).then(resetMessage => {
|
||||
if (resetMessage) {
|
||||
// report to the user is there was a problem during login
|
||||
|
||||
this.setState({resetSent: false, checkInputMessages: resetMessage, emailError: true});
|
||||
} else {
|
||||
this.setState({resetSent: true});
|
||||
const result = await dispatch(sendResetPassword({ email })).unwrap();
|
||||
if (result.success) {
|
||||
setResetSent(true);
|
||||
setEmailError(true);
|
||||
} else {
|
||||
setCheckInputMessages(result.message);
|
||||
console.error(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
var resetMessage = "";
|
||||
if (this.state.checkInputMessages.length) {
|
||||
resetMessage = (
|
||||
<Message compact warning icon>
|
||||
<Icon name="lightning" />
|
||||
<Message.Content>
|
||||
<Message.Header>Problems...</Message.Header>
|
||||
};
|
||||
|
||||
{this.state.checkInputMessages}
|
||||
|
||||
</Message.Content>
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
|
||||
<Grid verticalAlign='middle' centered>
|
||||
<Grid.Row>
|
||||
|
||||
<Grid.Column width="6">
|
||||
{ !this.state.resetSent ? (
|
||||
<div>
|
||||
<h2>Forgot your password?</h2>
|
||||
<Form className="raised padding segment" onSubmit={this.handleSubmit}>
|
||||
Enter the email address for your account.
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="email"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.emailError}
|
||||
label="Email"
|
||||
placeholder="Email..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Button
|
||||
size="large"
|
||||
content="Reset Password"
|
||||
onClick={this.handleSendResetPassword}
|
||||
fluid
|
||||
/>
|
||||
</Form>
|
||||
{resetMessage}
|
||||
</div>
|
||||
)
|
||||
:( <Segment raised padded='very'>
|
||||
<h2>Reset Link Sent </h2>
|
||||
<div>An email with a link to reset your password was sent to: {this.state.email}</div>
|
||||
<Link to="/" style={returnStyle}><Icon name="left arrow"/>Back to OpenMHz</Link>
|
||||
</Segment>
|
||||
)}
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
var resetMessage = "";
|
||||
if (checkInputMessages.length) {
|
||||
resetMessage = (
|
||||
<Message compact warning icon>
|
||||
<Icon name="lightning" />
|
||||
<Message.Content>
|
||||
<Message.Header>Problems...</Message.Header>
|
||||
{checkInputMessages}
|
||||
</Message.Content>
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid verticalAlign="middle" centered>
|
||||
<Grid.Row>
|
||||
<Grid.Column width={6}>
|
||||
{!resetSent ? (
|
||||
<div>
|
||||
<h2>Forgot your password?</h2>
|
||||
<Form className="raised padding segment">
|
||||
Enter the email address for your account.
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="email"
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
value={email}
|
||||
error={emailError}
|
||||
label="Email"
|
||||
placeholder="Email..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Button
|
||||
size="large"
|
||||
content="Reset Password"
|
||||
onClick={handleSendResetPassword}
|
||||
fluid
|
||||
/>
|
||||
</Form>
|
||||
{resetMessage}
|
||||
</div>
|
||||
) : (
|
||||
<Segment raised padded="very">
|
||||
<h2>Reset Link Sent </h2>
|
||||
<div>
|
||||
An email with a link to reset your password was sent to: {email}
|
||||
</div>
|
||||
<Link to="/" style={returnStyle}>
|
||||
<Icon name="left arrow" />
|
||||
Back to OpenMHz
|
||||
</Link>
|
||||
</Segment>
|
||||
)}
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default SendResetPassword;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import SendResetPassword from "./SendResetPassword"
|
||||
import * as userActions from "./user-actions"
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(SendResetPassword)
|
||||
@@ -1,28 +1,25 @@
|
||||
import React, { Component } from "react"
|
||||
import {
|
||||
Container,
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
import React from "react";
|
||||
import { Container, Message, Icon } from "semantic-ui-react";
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
class SentConfirmEmail extends Component {
|
||||
const SentConfirmEmail = ({ user }) => {
|
||||
const { email } = useSelector((state) => state.user);
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
<Message icon>
|
||||
<Icon name='envelope outline' />
|
||||
<Message.Content>
|
||||
<Message.Header>Confirmation email sent!</Message.Header>
|
||||
<p>
|
||||
We sent an email to the address you gave us: {email}<br />
|
||||
Please click on the link in the email to verify the email address.
|
||||
</p>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
<Message icon>
|
||||
<Icon name='envelope outline' />
|
||||
<Message.Content>
|
||||
<Message.Header>Confirmation email sent!</Message.Header>
|
||||
<p>We sent an email to the address you gave us: {this.props.user.email}<br />
|
||||
Please click on the link in the email to verify the email address.</p>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SentConfirmEmail
|
||||
export default SentConfirmEmail;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import SentConfirmEmail from "./SentConfirmEmail"
|
||||
import * as userActions from "./user-actions"
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(SentConfirmEmail)
|
||||
@@ -1,84 +1,63 @@
|
||||
import React, { Component } from "react";
|
||||
import {
|
||||
Segment,
|
||||
Container,
|
||||
Button
|
||||
} from "semantic-ui-react";
|
||||
import React, { useState } from "react";
|
||||
import { Segment, Container, Button } from "semantic-ui-react";
|
||||
|
||||
class Terms extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleAccept = this.handleAccept.bind(this);
|
||||
this.handleDecline = this.handleDecline.bind(this);
|
||||
}
|
||||
const Terms = ({ user, acceptTerms }) => {
|
||||
const [requestMessage, setRequestMessage] = useState(null);
|
||||
|
||||
const handleAccept = () => {
|
||||
acceptTerms(user.userId).then((requestMessage) => {
|
||||
if (requestMessage) {
|
||||
setRequestMessage(requestMessage);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDecline = () => {};
|
||||
|
||||
handleAccept() {
|
||||
this.props
|
||||
.acceptTerms(this.props.user.userId)
|
||||
.then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
requestMessage
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
<Segment attached>
|
||||
The agreement is useful to The Guardian to set the rules and guidelines for visitors, users and customers:
|
||||
|
||||
handleDecline() {
|
||||
Registration: inform users that they must agree to a set of rules when they register on your website or mobile app
|
||||
Disable or terminate accounts: inform users that their accounts can be disabled or terminated if abuses happen on your website or mobile app, e.g. spamming, swearing etc.
|
||||
Owner of content: notify users that you, the company, is the owner of the content appearing on your website or mobile app – except in cases where other users can post content (upload, share, create etc.) where the users are the owners of such content
|
||||
Changes to the agreement: users should be informed about upcoming changes to the agreement before the changes are applied.
|
||||
DMCA: The Guardian is also informing owners of copyright content that they can submit a DMCA notice to the website if they found infringing content in their DMCA clause.
|
||||
And more
|
||||
These are only a few examples of what kind of clauses and disclosures you can add in this kind of agreement.
|
||||
|
||||
}
|
||||
What is a Terms of Use
|
||||
While a Terms of Use is recommended to have, it’s not mandatory by law as the Privacy Policy agreement is required.
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
This agreement serves a single purpose: to inform users of rules that they must agree to. A Privacy Policy must inform users about your privacy practices (what data you collect, why, how, etc.)
|
||||
|
||||
<Container text>
|
||||
<Segment attached>
|
||||
The agreement is useful to The Guardian to set the rules and guidelines for visitors, users and customers:
|
||||
The definition of a Terms of Use agreement is simple:
|
||||
|
||||
Registration: inform users that they must agree to a set of rules when they register on your website or mobile app
|
||||
Disable or terminate accounts: inform users that their accounts can be disabled or terminated if abuses happen on your website or mobile app, e.g. spamming, swearing etc.
|
||||
Owner of content: notify users that you, the company, is the owner of the content appearing on your website or mobile app – except in cases where other users can post content (upload, share, create etc.) where the users are the owners of such content
|
||||
Changes to the agreement: users should be informed about upcoming changes to the agreement before the changes are applied.
|
||||
DMCA: The Guardian is also informing owners of copyright content that they can submit a DMCA notice to the website if they found infringing content in their DMCA clause.
|
||||
And more
|
||||
These are only a few examples of what kind of clauses and disclosures you can add in this kind of agreement.
|
||||
Terms of service (also known as terms of use and terms and conditions, commonly abbreviated as ToS or TOS and TOU) are rules which one must agree to abide by in order to use a service.
|
||||
|
||||
What is a Terms of Use
|
||||
While a Terms of Use is recommended to have, it’s not mandatory by law as the Privacy Policy agreement is required.
|
||||
Wikipedia
|
||||
Because this agreement simply acts as a contract between you, the company, and the users using or accessing your website or mobile app, the agreement can be named as you’d like:
|
||||
|
||||
This agreement serves a single purpose: to inform users of rules that they must agree to. A Privacy Policy must inform users about your privacy practices (what data you collect, why, how, etc.)
|
||||
|
||||
The definition of a Terms of Use agreement is simple:
|
||||
|
||||
Terms of service (also known as terms of use and terms and conditions, commonly abbreviated as ToS or TOS and TOU) are rules which one must agree to abide by in order to use a service.
|
||||
|
||||
Wikipedia
|
||||
Because this agreement simply acts as a contract between you, the company, and the users using or accessing your website or mobile app, the agreement can be named as you’d like:
|
||||
|
||||
Terms of Use (ToU)
|
||||
Terms of Service (ToS)
|
||||
Terms and Conditions (T&C)
|
||||
User Agreement
|
||||
Conditions of Use
|
||||
And so on
|
||||
The “Conditions of Use” of Amazon could be named “Terms of Use”:
|
||||
Terms of Use (ToU)
|
||||
Terms of Service (ToS)
|
||||
Terms and Conditions (T&C)
|
||||
User Agreement
|
||||
Conditions of Use
|
||||
And so on
|
||||
The “Conditions of Use” of Amazon could be named “Terms of Use”:
|
||||
</Segment>
|
||||
<Button.Group attached="bottom">
|
||||
<Button
|
||||
onClick={this.handleDecline}
|
||||
>Decline</Button>
|
||||
<Button.Or />
|
||||
<Button positive
|
||||
onClick={this.handleAccept}
|
||||
>Accept</Button>
|
||||
</Button.Group>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
<Button.Group attached="bottom">
|
||||
<Button onClick={handleDecline}>Decline</Button>
|
||||
<Button.Or />
|
||||
<Button positive onClick={handleAccept}>
|
||||
Accept
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Terms;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as userActions from "./user-actions"
|
||||
import Terms from "./Terms"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(Terms)
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
import React, { useEffect, useState, } from "react";
|
||||
import {
|
||||
Form,
|
||||
Dimmer,
|
||||
@@ -10,270 +11,266 @@ import {
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
class UserForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.checkInputs = this.checkInputs.bind(this);
|
||||
this.render = this.render.bind(this)
|
||||
|
||||
this.state = {
|
||||
checkInputMessages: [],
|
||||
registerMessages: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
screenName: "",
|
||||
email: "",
|
||||
location: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
firstNameError: false,
|
||||
lastNameError: false,
|
||||
screenNameError: false,
|
||||
emailError: false,
|
||||
locationError: false,
|
||||
passwordError: false,
|
||||
confirmPasswordError: false,
|
||||
isEditing: this.props.isEditing
|
||||
};
|
||||
if (this.props.isEditing) {
|
||||
this.state.firstName = this.props.user.firstName;
|
||||
this.state.lastName = this.props.user.lastName;
|
||||
this.state.screenName = this.props.user.screenName;
|
||||
this.state.email = this.props.user.email;
|
||||
this.state.location = this.props.user.location;
|
||||
const UserForm = (props) => {
|
||||
const [checkInputMessages, setCheckInputMessages] = useState([]);
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [screenName, setScreenName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [location, setLocation] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [firstNameError, setFirstNameError] = useState(false);
|
||||
const [lastNameError, setLastNameError] = useState(false);
|
||||
const [screenNameError, setScreenNameError] = useState(false);
|
||||
const [emailError, setEmailError] = useState(false);
|
||||
const [locationError, setLocationError] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
const [confirmPasswordError, setConfirmPasswordError] = useState(false);
|
||||
const [isEditing] = useState(props.isEditing);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const requestMessage = props.requestMessage;
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isEditing) {
|
||||
setFirstName(props.user.firstName);
|
||||
setLastName(props.user.lastName);
|
||||
setScreenName(props.user.screenName);
|
||||
setEmail(props.user.email);
|
||||
setLocation(props.user.location);
|
||||
setChanged(false);
|
||||
}
|
||||
}
|
||||
}, [props.isEditing, props.user]);
|
||||
|
||||
|
||||
|
||||
checkInputs() {
|
||||
const checkInputs = () => {
|
||||
let error = false;
|
||||
var checkInputMessages = [];
|
||||
let inputMessages = [];
|
||||
|
||||
if (this.state.firstName === "") {
|
||||
this.setState({ firstNameError: true });
|
||||
checkInputMessages.push("First Name is required");
|
||||
if (firstName === "") {
|
||||
setFirstNameError(true);
|
||||
inputMessages.push("First Name is required");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ firstNameError: false });
|
||||
setFirstNameError(false);
|
||||
}
|
||||
|
||||
if (this.state.lastName === "") {
|
||||
this.setState({ lastNameError: true });
|
||||
checkInputMessages.push("Last Name is required");
|
||||
if (lastName === "") {
|
||||
setLastNameError(true);
|
||||
inputMessages.push("Last Name is required");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ lastNameError: false });
|
||||
setLastNameError(false);
|
||||
}
|
||||
|
||||
if (this.state.email === "") {
|
||||
this.setState({ emailError: true });
|
||||
checkInputMessages.push("Email is required");
|
||||
if (email === "") {
|
||||
setEmailError(true);
|
||||
inputMessages.push("Email is required");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ emailError: false });
|
||||
setEmailError(false);
|
||||
}
|
||||
|
||||
if (this.state.location === "") {
|
||||
this.setState({ locationError: true });
|
||||
checkInputMessages.push("Location is required");
|
||||
if (location === "") {
|
||||
setLocationError(true);
|
||||
inputMessages.push("Location is required");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ locationError: false });
|
||||
setLocationError(false);
|
||||
}
|
||||
|
||||
if (this.state.screenName === "") {
|
||||
this.setState({ screenNameError: true });
|
||||
checkInputMessages.push("Screen Name is required");
|
||||
if (screenName === "") {
|
||||
setScreenNameError(true);
|
||||
inputMessages.push("Screen Name is required");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ screenNameError: false });
|
||||
setScreenNameError(false);
|
||||
}
|
||||
|
||||
if (this.props.isEditing) {
|
||||
this.setState({ checkInputMessages: checkInputMessages });
|
||||
if (props.isEditing) {
|
||||
setCheckInputMessages(checkInputMessages);
|
||||
return error;
|
||||
}
|
||||
|
||||
if (this.state.password === "") {
|
||||
this.setState({ passwordError: true });
|
||||
checkInputMessages.push("Password is required");
|
||||
if (password === "") {
|
||||
setPasswordError(true);
|
||||
inputMessages.push("Password is required");
|
||||
error = true;
|
||||
} if (this.state.password.length < 7) {
|
||||
this.setState({ passwordError: true });
|
||||
checkInputMessages.push("Password must be 7 charecters or more");
|
||||
} if (password.length < 7) {
|
||||
setPasswordError(true);
|
||||
inputMessages.push("Password must be 7 charecters or more");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ passwordError: false });
|
||||
setPasswordError(false);
|
||||
}
|
||||
|
||||
if (this.state.password !== this.state.confirmPassword) {
|
||||
this.setState({ confirmPasswordError: true });
|
||||
checkInputMessages.push("The passwords did not match");
|
||||
if (password !== confirmPassword) {
|
||||
setConfirmPasswordError(true);
|
||||
inputMessages.push("The passwords did not match");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ confirmPasswordError: false });
|
||||
setConfirmPasswordError(false);
|
||||
}
|
||||
|
||||
this.setState({ checkInputMessages: checkInputMessages });
|
||||
setCheckInputMessages(inputMessages);
|
||||
return error;
|
||||
}
|
||||
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
|
||||
handleSubmit(event) {
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
let inputError = this.checkInputs();
|
||||
let inputError = checkInputs();
|
||||
|
||||
if (!inputError) {
|
||||
const user = (({ firstName, lastName, screenName, location, email, password }) => ({ firstName, lastName, screenName, location, email, password }))(this.state);
|
||||
this.props.onSubmit(user);
|
||||
const user ={ firstName, lastName, screenName, location, email, password };
|
||||
setChanged(false);
|
||||
props.onSubmit(user);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
var registerMessage = "";
|
||||
var floatStyle = {
|
||||
overflow: 'auto'
|
||||
}
|
||||
var dimmerProps = {};
|
||||
if (this.props.isWaiting) {
|
||||
dimmerProps["active"] = true;
|
||||
}
|
||||
const { isEditing } = this.props;
|
||||
if (this.state.checkInputMessages.length || this.props.requestMessage.length) {
|
||||
var counter = 0;
|
||||
registerMessage = (
|
||||
<Message compact warning icon>
|
||||
<Icon name="lightning" />
|
||||
<Message.Content>
|
||||
<Message.Header>Problems...</Message.Header>
|
||||
<ul>
|
||||
{this.state.checkInputMessages.map(function (listValue) {
|
||||
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
|
||||
})}
|
||||
{this.props.requestMessage.length > 0 &&
|
||||
<li key="req-mess">{this.props.requestMessage}</li>
|
||||
}
|
||||
</ul>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Form
|
||||
className="raised padding segment"
|
||||
onSubmit={this.handleSubmit}
|
||||
>
|
||||
<Dimmer {...dimmerProps}>
|
||||
<Loader indeterminate>Updating Profile</Loader>
|
||||
</Dimmer>
|
||||
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="firstName"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.firstNameError}
|
||||
defaultValue={this.state.firstName}
|
||||
label="First name"
|
||||
placeholder="First Name..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="lastName"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.lastNameError}
|
||||
defaultValue={this.state.lastName}
|
||||
label="Last name"
|
||||
placeholder="Last Name..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="email"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.emailError}
|
||||
defaultValue={this.state.email}
|
||||
disabled={isEditing}
|
||||
label="Email"
|
||||
placeholder="Email..."
|
||||
/>
|
||||
var registerMessage = "";
|
||||
var floatStyle = {
|
||||
overflow: 'auto'
|
||||
}
|
||||
var dimmerProps = {};
|
||||
if (props.isWaiting) {
|
||||
dimmerProps["active"] = true;
|
||||
}
|
||||
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="screenName"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.screenNameError}
|
||||
defaultValue={this.state.screenName}
|
||||
label="Screen Name"
|
||||
placeholder="Screen Name..."
|
||||
/>
|
||||
<p style={{ fontStyle: "italic" }}>
|
||||
Shown on OpenMHz for each of your systems
|
||||
</p>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="location"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.locationError}
|
||||
defaultValue={this.state.location}
|
||||
label="General Location"
|
||||
placeholder="City, State..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
{isEditing !== true && (
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
name="password"
|
||||
type="password"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.passwordError}
|
||||
disabled={isEditing}
|
||||
label="Password"
|
||||
placeholder="Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.confirmPasswordError}
|
||||
disabled={isEditing}
|
||||
label="Confirm Password"
|
||||
placeholder="Confirm Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
)}
|
||||
<div style={floatStyle}>
|
||||
<Button type="submit" size="large" floated="right" value="Login" color='blue'>
|
||||
{isEditing !== true ? 'Register' : 'Update'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
{registerMessage}
|
||||
</div>
|
||||
if (checkInputMessages.length || requestMessage.length) {
|
||||
var counter = 0;
|
||||
registerMessage = (
|
||||
<Message compact warning icon>
|
||||
<Icon name="lightning" />
|
||||
<Message.Content>
|
||||
<Message.Header>Problems...</Message.Header>
|
||||
<ul>
|
||||
{checkInputMessages.map(function (listValue) {
|
||||
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
|
||||
})}
|
||||
{requestMessage.length > 0 &&
|
||||
<li key="req-mess">{requestMessage}</li>
|
||||
}
|
||||
</ul>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form
|
||||
className="raised padding segment"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Dimmer {...dimmerProps}>
|
||||
<Loader indeterminate>Updating Profile</Loader>
|
||||
</Dimmer>
|
||||
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="firstName"
|
||||
onChange={e => {setFirstName(e.target.value); setChanged(true)}}
|
||||
error={firstNameError}
|
||||
value={firstName}
|
||||
label="First name"
|
||||
placeholder="First Name..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="lastName"
|
||||
onChange={e => {setLastName(e.target.value); setChanged(true)}}
|
||||
error={lastNameError}
|
||||
value={lastName}
|
||||
label="Last name"
|
||||
placeholder="Last Name..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="email"
|
||||
onChange={e => {setEmail(e.target.value); setChanged(true)}}
|
||||
error={emailError}
|
||||
value={email}
|
||||
disabled={isEditing}
|
||||
label="Email"
|
||||
placeholder="Email..."
|
||||
/>
|
||||
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="screenName"
|
||||
onChange={e => {setScreenName(e.target.value); setChanged(true)}}
|
||||
error={screenNameError}
|
||||
value={screenName}
|
||||
label="Screen Name"
|
||||
placeholder="Screen Name..."
|
||||
/>
|
||||
<p style={{ fontStyle: "italic" }}>
|
||||
Shown on OpenMHz for each of your systems
|
||||
</p>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="location"
|
||||
onChange={e => {setLocation(e.target.value); setChanged(true)}}
|
||||
error={locationError}
|
||||
value={location}
|
||||
label="General Location"
|
||||
placeholder="City, State..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
{isEditing !== true && (
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
name="password"
|
||||
type="password"
|
||||
onChange={e => {setPassword(e.target.value); setChanged(true)}}
|
||||
value={password}
|
||||
error={passwordError}
|
||||
disabled={isEditing}
|
||||
label="Password"
|
||||
placeholder="Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
onChange={e => {setConfirmPassword(e.target.value); setChanged(true)}}
|
||||
value={confirmPassword}
|
||||
error={confirmPasswordError}
|
||||
disabled={isEditing}
|
||||
label="Confirm Password"
|
||||
placeholder="Confirm Password..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
)}
|
||||
<div style={floatStyle}>
|
||||
<Button type="submit" size="large" floated="right" value="Login" color='blue' disabled={!changed}>
|
||||
{isEditing !== true ? 'Register' : 'Update'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
{registerMessage}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default UserForm;
|
||||
|
||||
@@ -1,52 +1,42 @@
|
||||
import React, { Component } from "react"
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Button,
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
class WaitConfirmEmail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSendConfirmEmail = this.handleSendConfirmEmail.bind(this);
|
||||
import { sendConfirmEmail } from "../features/user/userSlice";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { Container, Header, Button, Message, Icon } from "semantic-ui-react";
|
||||
|
||||
}
|
||||
handleSendConfirmEmail(event) {
|
||||
this.props.sendConfirmEmail(this.props.user.userId).then(loginMessage => {
|
||||
if (loginMessage) {
|
||||
// report to the user is there was a problem during login
|
||||
this.setState({
|
||||
loginMessage
|
||||
});
|
||||
}
|
||||
});
|
||||
const WaitConfirmEmail = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { email, userId } = useSelector((state) => state.user);
|
||||
|
||||
const handleSendConfirmEmail = async () => {
|
||||
await dispatch(sendConfirmEmail(userId)).unwrap();
|
||||
navigate("/sent-confirm-email")
|
||||
};
|
||||
|
||||
render() {
|
||||
return(
|
||||
<Container text>
|
||||
<Message icon>
|
||||
<Icon name='envelope outline'/>
|
||||
<Message.Content>
|
||||
<Message.Header>Confirm your email address</Message.Header>
|
||||
<p>We sent an email to the address you gave us: {this.props.user.email}<br/>
|
||||
Please click on the link in the email to verify the email address.</p>
|
||||
<Header as='h3'>Didn't get an email?</Header>
|
||||
<p>So, you have waited a bit and haven't gotten anything?</p>
|
||||
|
||||
return (
|
||||
<Container text>
|
||||
<Message icon>
|
||||
<Icon name="envelope outline" />
|
||||
<Message.Content>
|
||||
<Message.Header>Confirm your email address</Message.Header>
|
||||
<p>
|
||||
We sent an email to the address you gave us: {email}
|
||||
<br />
|
||||
Please click on the link in the email to verify the email address.
|
||||
</p>
|
||||
<Header as="h3">Didn't get an email?</Header>
|
||||
<p>So, you have waited a bit and haven't gotten anything?</p>
|
||||
<p>...and you checked your spam folder?</p>
|
||||
<p>Well, click below.</p>
|
||||
<Button
|
||||
size="large"
|
||||
content="Resend Email Confirmation"
|
||||
onClick={this.handleSendConfirmEmail}
|
||||
/>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
<Button
|
||||
size="large"
|
||||
content="Resend Email Confirmation"
|
||||
onClick={handleSendConfirmEmail}
|
||||
/>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default WaitConfirmEmail
|
||||
export default WaitConfirmEmail;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import WaitConfirmEmail from "./WaitConfirmEmail"
|
||||
import * as userActions from "./user-actions"
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(WaitConfirmEmail)
|
||||
185
account/src/features/api/apiSlice.js
Normal file
185
account/src/features/api/apiSlice.js
Normal file
@@ -0,0 +1,185 @@
|
||||
// Import the RTK Query methods from the React-specific entry point
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
|
||||
|
||||
// Define our single API slice object
|
||||
export const apiSlice = createApi({
|
||||
// The cache reducer expects to be added at `state.api` (already default - this is optional)
|
||||
reducerPath: 'api',
|
||||
// All of our requests will have URLs starting with '/fakeApi'
|
||||
baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_ADMIN_SERVER }),
|
||||
// The "endpoints" represent operations and requests for this server
|
||||
endpoints: builder => ({
|
||||
// The `getPosts` endpoint is a "query" operation that returns data
|
||||
getTalkgroups: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `/talkgroups/${shortName}`, credentials: "include" })
|
||||
}),
|
||||
getGroups: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `/groups/${shortName}`, credentials: "include" }),
|
||||
providesTags: (result, error, arg) =>
|
||||
result
|
||||
? [...result.map(({ _id }) => ({ type: 'Group', _id })), 'Group']
|
||||
: ['Group'],
|
||||
}),
|
||||
|
||||
getErrors: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `${process.env.REACT_APP_BACKEND_SERVER}/${shortName}/errors` })
|
||||
}),
|
||||
updateGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}/${group._id}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: group,
|
||||
}),
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: updatedGroup } = await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', updatedGroup.shortName, (groups) => {
|
||||
const groupIndex = groups.findIndex((obj => obj.groupId === updatedGroup.groupId));
|
||||
groups[groupIndex] = updatedGroup;
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
createGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: group,
|
||||
}),
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newGroup } = await queryFulfilled;
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', newGroup.shortName, (groups) => {
|
||||
groups.push(newGroup);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
deleteGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}/${group._id}`,
|
||||
method: 'DELETE',
|
||||
credentials: "include"
|
||||
}),
|
||||
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', group.shortName, (groups) => {
|
||||
const groupIndex = groups.findIndex((obj => obj.groupId === group.groupId));
|
||||
groups.splice(groupIndex,1);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
saveGroupOrder: builder.mutation({
|
||||
query: ({shortName, order}) => ({
|
||||
url: `/groups/${shortName}/reorder`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: order,
|
||||
}),
|
||||
|
||||
//invalidatesTags: (result, error, group) => ['Group'],
|
||||
}),
|
||||
importTalkgroups: builder.mutation({
|
||||
query: ({shortName,file}) => ({
|
||||
url: `${process.env.REACT_APP_ADMIN_SERVER}/talkgroups/${shortName}/import`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: file,
|
||||
}),
|
||||
|
||||
async onQueryStarted({shortName,file}, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newTalkgroups } = await queryFulfilled;
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getTalkgroups', shortName, (talkgroups) => {
|
||||
talkgroups.length = 0; // Clear your array
|
||||
talkgroups.push(...newTalkgroups);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
//invalidatesTags: (result, error, group) => ['Group'],
|
||||
}),
|
||||
getSystems: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: () => ({ url: `/systems`, credentials: "include" })
|
||||
}),
|
||||
createSystem: builder.mutation({
|
||||
query: (system) => ({
|
||||
url: `/systems`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: system,
|
||||
}),
|
||||
async onQueryStarted(system, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newSystem } = await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
systems.push(newSystem);
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
updateSystem: builder.mutation({
|
||||
query: (system) => ({
|
||||
url: `/systems/${system.shortName}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: system,
|
||||
}),
|
||||
async onQueryStarted(system, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: updatedSystem } = await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
const systemIndex = systems.findIndex((obj => obj.shortName === updatedSystem.shortName));
|
||||
systems[systemIndex] = updatedSystem;
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
deleteSystem: builder.mutation({
|
||||
query: (shortName) => ({
|
||||
url: `/systems/${shortName}`,
|
||||
method: 'DELETE',
|
||||
credentials: "include"
|
||||
}),
|
||||
async onQueryStarted(shortName, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
const systemIndex = systems.findIndex((obj => obj.shortName === shortName));
|
||||
systems.splice(systemIndex, 1);
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
// Export the auto-generated hook for the `getPosts` query endpoint
|
||||
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice
|
||||
256
account/src/features/user/userSlice.js
Normal file
256
account/src/features/user/userSlice.js
Normal file
@@ -0,0 +1,256 @@
|
||||
import {
|
||||
createSlice,
|
||||
createAsyncThunk
|
||||
} from '@reduxjs/toolkit'
|
||||
|
||||
|
||||
export const authenticateUser = createAsyncThunk(
|
||||
'user/authenticate',
|
||||
async ({ getState }) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/authenticated"
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: "include"
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
export const loginUser = createAsyncThunk(
|
||||
'user/login',
|
||||
async (data) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/login"
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
export const registerUser = createAsyncThunk(
|
||||
'user/register',
|
||||
async (data) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/register"
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
export const updateProfile = createAsyncThunk(
|
||||
'user/updateProfile',
|
||||
async (user) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + user.userId;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(user)
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
export const logoutUser = createAsyncThunk(
|
||||
'user/logout',
|
||||
async ({ getState }) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/logout"
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: "include"
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
export const confirmEmail = createAsyncThunk(
|
||||
'user/confirmEmail',
|
||||
async ({userId, token}) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/confirm/" + token;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST'
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
export const sendConfirmEmail = createAsyncThunk(
|
||||
'user/sendConfirmEmail',
|
||||
async (userId) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/send-confirm";
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
export const resetPassword = createAsyncThunk(
|
||||
'user/resetPassword',
|
||||
async (data) => {
|
||||
const {userId, token, password} = data;
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/reset-password/" + token
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({password})
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
export const sendResetPassword = createAsyncThunk(
|
||||
'user/sendResetPassword',
|
||||
async (data) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/api/send-reset-password"
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
export const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState: {
|
||||
isWaiting: false,
|
||||
authenticated: false,
|
||||
hasAuthenticated: false,
|
||||
userId: "",
|
||||
admin: false,
|
||||
email: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
location: "",
|
||||
screenName: ""
|
||||
},
|
||||
|
||||
reducers: {
|
||||
increment: (state) => {
|
||||
// Redux Toolkit allows us to write "mutating" logic in reducers. It
|
||||
// doesn't actually mutate the state because it uses the Immer library,
|
||||
// which detects changes to a "draft state" and produces a brand new
|
||||
// immutable state based off those changes
|
||||
state.value += 1
|
||||
},
|
||||
},
|
||||
extraReducers: {
|
||||
[loginUser.fulfilled]: (state, data) => {
|
||||
const {payload} = data;
|
||||
if (payload.success) {
|
||||
state.authenticated = true;
|
||||
state.hasAuthenticated = true;
|
||||
state.userId = payload.user.userId;
|
||||
state.admin = payload.user.admin;
|
||||
state.email = payload.user.email;
|
||||
state.firstName = payload.user.firstName;
|
||||
state.lastName = payload.user.lastName;
|
||||
state.location = payload.user.location;
|
||||
state.screenName = payload.user.screenName;
|
||||
} else {
|
||||
state.authenticated = false;
|
||||
state.hasAuthenticated = true;
|
||||
}
|
||||
},
|
||||
[updateProfile.fulfilled]: (state, data) => {
|
||||
const {payload} = data;
|
||||
if (payload.success) {
|
||||
state.firstName = payload.user.firstName;
|
||||
state.lastName = payload.user.lastName;
|
||||
state.location = payload.user.location;
|
||||
state.screenName = payload.user.screenName;
|
||||
}
|
||||
},
|
||||
[loginUser.rejected]: (state, { payload }) => {
|
||||
state.authenticated = false;
|
||||
state.hasAuthenticated = true;
|
||||
},
|
||||
[authenticateUser.fulfilled]: (state, data) => {
|
||||
const {payload} = data;
|
||||
if (payload.success) {
|
||||
state.authenticated = true;
|
||||
state.hasAuthenticated = true;
|
||||
state.userId = payload.user.userId;
|
||||
state.admin = payload.user.admin;
|
||||
state.email = payload.user.email;
|
||||
state.firstName = payload.user.firstName;
|
||||
state.lastName = payload.user.lastName;
|
||||
state.location = payload.user.location;
|
||||
state.screenName = payload.user.screenName;
|
||||
} else {
|
||||
state.authenticated = false;
|
||||
state.hasAuthenticated = true;
|
||||
}
|
||||
},
|
||||
[authenticateUser.rejected]: (state, { payload }) => {
|
||||
state.authenticated = false;
|
||||
state.hasAuthenticated = true;
|
||||
|
||||
},
|
||||
[logoutUser.fulfilled]: (state, { payload }) => {
|
||||
if (payload.success) {
|
||||
state.authenticated = false;
|
||||
state.userId = null;
|
||||
}
|
||||
},
|
||||
[loginUser.fulfilled]: (state, { payload }) => {
|
||||
if (payload.success) {
|
||||
state.authenticated = false;
|
||||
state.userId = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Extract the action creators object and the reducer
|
||||
export const userReducer = userSlice.reducer;
|
||||
@@ -1,48 +1,22 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux'
|
||||
import { Route, Switch } from 'react-router' // react-router v4/v5
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import configureStore, { history } from './redux-router/configureStore'
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import setupStore from './redux-router/configureStore'
|
||||
import 'semantic-ui-css/semantic.min.css'
|
||||
import App from "./App/App"
|
||||
import Restricted from "./App/Restricted"
|
||||
|
||||
import LoginContainer from "./User/LoginContainer"
|
||||
import RegisterContainer from "./User/RegisterContainer"
|
||||
import ProfileContainer from "./User/ProfileContainer"
|
||||
import ConfirmEmailContainer from "./User/ConfirmEmailContainer"
|
||||
import WaitConfirmEmailContainer from "./User/WaitConfirmEmailContainer"
|
||||
import SentConfirmEmailContainer from "./User/SentConfirmEmailContainer"
|
||||
import SendResetPasswordContainer from "./User/SendResetPasswordContainer"
|
||||
import ResetPasswordContainer from "./User/ResetPasswordContainer"
|
||||
import TermsContainer from "./User/TermsContainer"
|
||||
|
||||
const store = configureStore(/* provide initial state if any */)
|
||||
const store = setupStore(/* provide initial state if any */)
|
||||
|
||||
console.log(process.env);
|
||||
//This thssi
|
||||
ReactDOM.render(
|
||||
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
|
||||
<>
|
||||
<App>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Restricted(ProfileContainer, store)} />
|
||||
<Route path="/login" component={LoginContainer} />
|
||||
<Route path="/register" component={RegisterContainer} />
|
||||
<Route path="/wait-confirm-email" component={WaitConfirmEmailContainer} />
|
||||
<Route path="/sent-confirm-email" component={SentConfirmEmailContainer} />
|
||||
<Route path="/send-reset-password" component={SendResetPasswordContainer} />
|
||||
<Route path="/reset-password/:userId/:token" component={ResetPasswordContainer} />
|
||||
<Route path="/confirm-email/:userId/:token" component={ConfirmEmailContainer} />
|
||||
<Route path="/terms" component={Restricted(TermsContainer, store)} />
|
||||
<Route path="/profile" component={Restricted(ProfileContainer, store)} />
|
||||
|
||||
</Switch>
|
||||
</App>
|
||||
</>
|
||||
</ConnectedRouter>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
)
|
||||
@@ -1,26 +1,20 @@
|
||||
// https://github.com/supasate/connected-react-router
|
||||
// configureStore.js
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import { userReducer } from '../features/user/userSlice'
|
||||
import { apiSlice } from "../features/api/apiSlice"
|
||||
|
||||
import { createBrowserHistory } from 'history'
|
||||
import { applyMiddleware, compose, createStore } from 'redux'
|
||||
import { routerMiddleware } from 'connected-react-router'
|
||||
import createRootReducer from './reducers'
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
export const history = createBrowserHistory()
|
||||
|
||||
export default function configureStore(preloadedState) {
|
||||
const store = createStore(
|
||||
createRootReducer(history), // root reducer with router state
|
||||
const setupStore = (preloadedState) => {
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
user: userReducer,
|
||||
[apiSlice.reducerPath]: apiSlice.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
|
||||
getDefaultMiddleware().concat(apiSlice.middleware),
|
||||
preloadedState,
|
||||
compose(
|
||||
applyMiddleware(
|
||||
routerMiddleware(history), // for dispatching history actions
|
||||
thunk,
|
||||
// ... other middlewares ...
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
return store
|
||||
}
|
||||
}
|
||||
|
||||
export default setupStore;
|
||||
13754
account/yarn.lock
13754
account/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
# build environment
|
||||
FROM node:13.12.0-alpine as build
|
||||
FROM node:19-alpine3.16 as build
|
||||
WORKDIR /app
|
||||
ENV PATH /app/node_modules/.bin:$PATH
|
||||
ARG REACT_APP_BACKEND_SERVER
|
||||
@@ -15,7 +15,7 @@ COPY . ./
|
||||
RUN npm run build
|
||||
|
||||
# production environment
|
||||
FROM node:13.12.0-alpine
|
||||
FROM node:19-alpine3.16
|
||||
RUN mkdir -p /app/public
|
||||
COPY ./package.json /tmp
|
||||
RUN cd /tmp && npm install
|
||||
|
||||
@@ -3,41 +3,42 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@visx/axis": "1.4.0",
|
||||
"@visx/curve": "1.0.0",
|
||||
"@visx/glyph": "1.0.0",
|
||||
"@visx/group": "1.0.0",
|
||||
"@visx/legend": "1.4.0",
|
||||
"@visx/responsive": "1.3.0",
|
||||
"@visx/scale": "1.4.0",
|
||||
"@visx/shape": "1.4.0",
|
||||
"bcrypt": "5.0.0",
|
||||
"@visx/axis": "3.0.1",
|
||||
"@visx/curve": "3.0.0",
|
||||
"@visx/glyph": "3.0.0",
|
||||
"@visx/group": "3.0.0",
|
||||
"@visx/legend": "3.0.0",
|
||||
"@visx/responsive": "3.0.0",
|
||||
"@visx/scale": "3.0.0",
|
||||
"@visx/shape": "3.0.0",
|
||||
"bcrypt": "5.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"connect-mongo": "3.2.0",
|
||||
"csv-parse": "4.14.2",
|
||||
"d3-time-format": "3.0.0",
|
||||
"express": "4.17.1",
|
||||
"express-session": "1.17.1",
|
||||
"mongoose": "5.11.11",
|
||||
"multer": "1.4.2",
|
||||
"passport": "0.4.1",
|
||||
"passport-local": "1.0.0"
|
||||
"connect-mongo": "^4.6.0",
|
||||
"csv-parse": "5.3.6",
|
||||
"d3-time-format": "4.1.0",
|
||||
"express": "4.18.2",
|
||||
"express-session": "1.17.3",
|
||||
"history": "^5.3.0",
|
||||
"mongoose": "7.0.0",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"passport": "0.6.0",
|
||||
"passport-local": "1.0.0",
|
||||
"redux-devtools-extension": "^2.13.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@reduxjs/toolkit": "^1.1.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"axios": "0.21.1",
|
||||
"connected-react-router": "6.8.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-scripts": "4.0.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"semantic-ui-css": "2.4.1",
|
||||
"semantic-ui-react": "2.0.1"
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"axios": "1.3.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "6.8.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"redux-thunk": "2.4.2",
|
||||
"semantic-ui-css": "2.5.0",
|
||||
"semantic-ui-react": "2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
||||
@@ -2,16 +2,15 @@ const path = require("path");
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const bodyParser = require("body-parser");
|
||||
const connectMongo = require("connect-mongo");
|
||||
const MongoStore = require("connect-mongo");
|
||||
const secrets = require("./secrets");
|
||||
|
||||
const MongoStore = connectMongo(session)
|
||||
var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var backend_server = process.env['REACT_APP_BACKEND_SERVER'] != null ? process.env['REACT_APP_BACKEND_SERVER'] : 'https://api.openmhz.com';
|
||||
var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com';
|
||||
var socket_server = process.env['REACT_APP_SOCKET_SERVER'] != null ? process.env['REACT_APP_SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var dev_server = frontend_server + ":3000"
|
||||
var dev_server = "http://admin.openmhz.test:3000"
|
||||
|
||||
module.exports = function(app, passport) {
|
||||
app.set("port", 3008)
|
||||
@@ -36,9 +35,12 @@ module.exports = function(app, passport) {
|
||||
secure: false,
|
||||
domain: cookie_domain
|
||||
},
|
||||
store: new MongoStore({
|
||||
url: secrets.db,
|
||||
autoReconnect: true
|
||||
store: MongoStore.create({
|
||||
mongoUrl: secrets.db,
|
||||
autoReconnect: true,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 240,
|
||||
touchAfter: 24 * 3600 // time period in seconds
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,13 @@ module.exports = function (app, passport) {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser((id, done) => {
|
||||
User.findById(id, (err, user) => {
|
||||
done(err, user)
|
||||
})
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
const user = await User.findById(id).exec();
|
||||
if (user) {
|
||||
done(null,user);
|
||||
} else {
|
||||
done("User not found", null);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -4,286 +4,246 @@ const mongoose = require("mongoose");
|
||||
|
||||
//router.get('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
|
||||
|
||||
exports.getGroups = function(req, res, next) {
|
||||
process.nextTick(function() {
|
||||
System.findOne(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function(err, system) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
if (!system) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
exports.getGroups = async function (req, res, next) {
|
||||
process.nextTick(async function () {
|
||||
const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
|
||||
if (!system) {
|
||||
res.status(404);
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.params.groupId) {
|
||||
var groupId = req.params.groupId.toLowerCase();
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.status(401)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Group.findOne(
|
||||
{
|
||||
_id: new mongoose.Types.ObjectId(groupId),
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
{ sort: { position: -1 } },
|
||||
function(err, group) {
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
res.json({
|
||||
success: true,
|
||||
group: group
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Group.find(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
}).sort("position").exec(
|
||||
function(err, groups) {
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
res.json({
|
||||
success: true,
|
||||
groups: groups
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (req.params.groupId) {
|
||||
var groupId = req.params.groupId.toLowerCase();
|
||||
|
||||
const group = await Group.findOne(
|
||||
{
|
||||
_id: new mongoose.Types.ObjectId(groupId),
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
{ sort: { position: -1 } });
|
||||
if (!group) {
|
||||
res.status(404);
|
||||
res.json({ success: false });
|
||||
return;
|
||||
|
||||
} else {
|
||||
res.json(group);
|
||||
return;
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
const group = await Group.find(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
}).sort("position").exec();
|
||||
if (!group) {
|
||||
res.status(404);
|
||||
res.json({ success: false });
|
||||
return;
|
||||
|
||||
} else {
|
||||
let result = group;
|
||||
if (!Array.isArray(group)) {
|
||||
result = [group]
|
||||
}
|
||||
res.json(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
exports.deleteGroup = function(req, res, next) {
|
||||
//router.get('/system/:shortName/remove_group/:groupId', isLoggedIn, function(req, res) {
|
||||
System.findOne({
|
||||
'shortName': req.params.shortName.toLowerCase()
|
||||
}, function(err, system) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
if (!system) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
Group.findOneAndRemove({
|
||||
shortName: req.params.shortName.toLowerCase(),
|
||||
'_id': new mongoose.Types.ObjectId(req.params.groupId)
|
||||
}, function(err) {
|
||||
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
// removed!
|
||||
});
|
||||
exports.deleteGroup = async function (req, res, next) {
|
||||
const system = await System.findOne({ 'shortName': req.params.shortName.toLowerCase() }).catch(err => {
|
||||
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
});
|
||||
if (!system) {
|
||||
console.error(err);
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.status(401);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
await Group.findOneAndRemove({
|
||||
shortName: req.params.shortName.toLowerCase(),
|
||||
'_id': new mongoose.Types.ObjectId(req.params.groupId)
|
||||
}).catch(err => {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
exports.reorderGroups = function(req, res, next) {
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
exports.reorderGroups = function (req, res, next) {
|
||||
console.log(req.body)
|
||||
//router.post('/system/:shortName/group_order', isLoggedIn, function(req, res) {
|
||||
process.nextTick(function() {
|
||||
System.findOne(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function(err, system) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
if (!system) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
process.nextTick(async function () {
|
||||
const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
|
||||
|
||||
var groupOrder = JSON.parse(req.body.groupOrder);
|
||||
for (var i = 0; i < groupOrder.length; i++) {
|
||||
if (!system) {
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.status(401);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(req.body)
|
||||
if (!req.body.groupOrder) {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Order data is wrong."
|
||||
});
|
||||
return;
|
||||
}
|
||||
var groupOrder = JSON.parse(req.body.groupOrder);
|
||||
for (var i = 0; i < groupOrder.length; i++) {
|
||||
|
||||
console.log("in group: " + groupOrder[i] + " position: " + i);
|
||||
Group.findOneAndUpdate(
|
||||
{
|
||||
_id: new mongoose.Types.ObjectId(groupOrder[i]),
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
{
|
||||
position: i
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
upsert: false
|
||||
},
|
||||
function(err, group) {
|
||||
console.log("Save group: " + group._id + " position: " + group.position);
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
|
||||
console.log(
|
||||
"Error: Group Order - " +
|
||||
err +
|
||||
" Shortname: " +
|
||||
req.params.shortName.toLowerCase()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
res.json({
|
||||
success: true
|
||||
console.log("in group: " + groupOrder[i] + " position: " + i);
|
||||
const group = await Group.findOneAndUpdate(
|
||||
{
|
||||
_id: new mongoose.Types.ObjectId(groupOrder[i]),
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
{
|
||||
position: i
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
upsert: false
|
||||
});
|
||||
if (!group) {
|
||||
res.status(404)
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
|
||||
console.log(
|
||||
"Error: Group Order - Shortname: " +
|
||||
req.params.shortName.toLowerCase()
|
||||
);
|
||||
return;
|
||||
}
|
||||
);
|
||||
}
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.upsertGroup = function(req, res, next) {
|
||||
exports.upsertGroup = function (req, res, next) {
|
||||
console.log(req.body)
|
||||
//router.post('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
|
||||
process.nextTick(function() {
|
||||
System.findOne(
|
||||
process.nextTick(async function () {
|
||||
console.log(req.body)
|
||||
let system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
|
||||
|
||||
if (!system) {
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.status(401);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
var message = "";
|
||||
var groupName = req.body.groupName.replace(/[^\w\s]/g, "");
|
||||
var talkgroups = JSON.parse(req.body.talkgroups);
|
||||
|
||||
if (!talkgroups.length || !Array.isArray(talkgroups)) {
|
||||
message = "Talkgroups need to be selected, group can not be empty ";
|
||||
}
|
||||
if (!groupName.length) {
|
||||
message = message + "Group Name is required";
|
||||
}
|
||||
|
||||
if (message.length) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: message
|
||||
});
|
||||
return;
|
||||
}
|
||||
// if we got here, that means that everything is good for updating/inserting
|
||||
|
||||
var groupId = req.params.groupId;
|
||||
|
||||
let group = await Group.findOneAndUpdate(
|
||||
{
|
||||
_id: new mongoose.Types.ObjectId(groupId),
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function(err, system) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
if (!system) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
var message = "";
|
||||
var groupName = req.body.groupName.replace(/[^\w\s]/g, "");
|
||||
var talkgroups = JSON.parse(req.body.talkgroups);
|
||||
{
|
||||
groupName: groupName,
|
||||
talkgroups: talkgroups
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
upsert: true
|
||||
}).catch(err => {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
res.json(group);
|
||||
return;
|
||||
|
||||
if (!talkgroups.length || !Array.isArray(talkgroups)) {
|
||||
message = "Talkgroups need to be selected, group can not be empty ";
|
||||
}
|
||||
if (!groupName.length) {
|
||||
message = message + "Group Name is required";
|
||||
}
|
||||
|
||||
if (message.length) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: message
|
||||
});
|
||||
return;
|
||||
}
|
||||
// if we got here, that means that everything is good for updating/inserting
|
||||
|
||||
var groupId = req.params.groupId;
|
||||
|
||||
Group.findOneAndUpdate(
|
||||
{
|
||||
_id: new mongoose.Types.ObjectId(groupId),
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
{
|
||||
groupName: groupName,
|
||||
talkgroups: talkgroups
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
upsert: true
|
||||
},
|
||||
function(err, group) {
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
res.json({
|
||||
success: true,
|
||||
group: group
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Permission = require("../models/permission");
|
||||
const System = require("../models/system");
|
||||
const User = require("../models/user");
|
||||
|
||||
exports.fetchPermissions = function(req, res, next) {
|
||||
Permission.find(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function(err, permissions) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
var userIds = permissions.map(permission => {return permission.userId});
|
||||
User.find(
|
||||
{
|
||||
_id: {
|
||||
$in: userIds
|
||||
}
|
||||
},
|
||||
function(err, users) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
var returnUsers = users.map(user =>{
|
||||
var permission = permissions.find(obj => {
|
||||
|
||||
if (obj.userId.toString() == user._id.toString()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (!permission) {
|
||||
console.error("Error - Permission not found for user: " + user._id);
|
||||
res.json({ success: false, message: "Permission not found" });
|
||||
return;
|
||||
}
|
||||
return {_id: permission._id, userId: user._id, firstName: user.firstName, lastName: user.lastName, email: user.email, role: permission.role };
|
||||
});
|
||||
|
||||
res.json({ success: true, permissions: returnUsers });
|
||||
});
|
||||
return;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
exports.addPermission = async (req, res, next) => {
|
||||
//router.post('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
|
||||
|
||||
|
||||
let role = req.body.role;
|
||||
var system = false;
|
||||
var user = false;
|
||||
var existing = false;
|
||||
var saved = false;
|
||||
if (!role || (role >= 20) || (role <1)) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Invalid Role"
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log("role looks good");
|
||||
try {
|
||||
system = await System.findOne(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
});
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!system) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That System does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log("system looks good")
|
||||
try {
|
||||
user = await User.findOne(
|
||||
{
|
||||
email: req.body.email
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
res.json({ success: false, message: "User not found" });
|
||||
return;
|
||||
}
|
||||
console.log("user looks good")
|
||||
try {
|
||||
existing = await Permission.findOne({userId: user._id, systemId: system._id});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
res.json({ success: false, message: "User already has acccess" });
|
||||
return;
|
||||
}
|
||||
console.log("no existing")
|
||||
var permission = new Permission({
|
||||
userId: user._id,
|
||||
systemId: system._id,
|
||||
shortName: system.shortName,
|
||||
role: role
|
||||
});
|
||||
|
||||
|
||||
try {
|
||||
saved = await permission.save()
|
||||
} catch(err) {
|
||||
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log("saved it")
|
||||
var newPermission = saved.toObject();
|
||||
newPermission.firstName = user.firstName;
|
||||
newPermission.lastName = user.lastName;
|
||||
newPermission.email = user.email;
|
||||
|
||||
|
||||
console.log("Add Permission to: " + system.shortName + " for: " + newPermission.email + " by: " + req.user.email);
|
||||
res.json({
|
||||
success: true,
|
||||
permission: newPermission
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.updatePermission = function(req, res, next) {
|
||||
process.nextTick(function() {
|
||||
Permission.findOne(
|
||||
{
|
||||
_id: new mongoose.Types.ObjectId(req.params.permissionId),
|
||||
shortName: req.params.shortName.toLowerCase(req.params.shortName)
|
||||
},
|
||||
|
||||
function(err, permission) {
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (permission.role >= 20) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Cannot change permissions of system owner"
|
||||
});
|
||||
return;
|
||||
}
|
||||
var newRole = req.body.role;
|
||||
if (!newRole || (newRole >= 20) || (newRole <1)) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Invalid Role"
|
||||
});
|
||||
return;
|
||||
}
|
||||
permission.role = newRole;
|
||||
permission.save(function(err, saved) {
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
permission: saved.toJSON()
|
||||
});
|
||||
return;
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.deletePermission = function(req, res, next) {
|
||||
//router.get('/system/:shortName/remove_group/:groupId', isLoggedIn, function(req, res) {
|
||||
|
||||
Permission.findOneAndRemove({
|
||||
shortName: req.params.shortName.toLowerCase(),
|
||||
'_id': new mongoose.Types.ObjectId(req.params.permissionId),
|
||||
role: { $lt : 20}
|
||||
}, function(err, permission) {
|
||||
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!permission) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Permission not found"
|
||||
});
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
// removed!
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
@@ -12,173 +12,133 @@ exports.isLoggedIn = function (req, res, next) {
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
exports.listSystems = function (req, res, next) {
|
||||
exports.listSystems = async function (req, res, next) {
|
||||
console.log("Listing Systems for: " + req.user._id);
|
||||
System.find({
|
||||
userId: mongoose.Types.ObjectId(req.user._id)
|
||||
},
|
||||
function (err, systems) {
|
||||
if (err) {
|
||||
console.error("Error - ListSystems: " + err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
const userId = new mongoose.Types.ObjectId(req.user._id);
|
||||
const systems = await System.find({ userId: userId }).exec();
|
||||
if (systems == null) {
|
||||
res.status(400);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
|
||||
var shortNames = new Array();
|
||||
for (var i = 0; i < systems.length; i++) {
|
||||
shortNames.push(systems[i].shortName);
|
||||
}
|
||||
let shortNames = new Array();
|
||||
for (var i = 0; i < systems.length; i++) {
|
||||
shortNames.push(systems[i].shortName);
|
||||
}
|
||||
|
||||
SystemStat.find({
|
||||
shortName: {
|
||||
$in: shortNames
|
||||
}
|
||||
},
|
||||
function (err, stats) {
|
||||
var returnSys = systems.map(obj => {
|
||||
var rObj = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}))(obj);
|
||||
if (obj.showScreenName) {
|
||||
rObj.screenName = req.user.screenName;
|
||||
} else {
|
||||
rObj.screenName = null;
|
||||
}
|
||||
rObj.id = obj._id;
|
||||
return rObj;
|
||||
});
|
||||
var sys_stats = {}
|
||||
var MS_PER_MINUTE = 60000;
|
||||
var now = new Date();
|
||||
for (var i = 0; i < stats.length; i++) {
|
||||
sys_stats[stats[i].shortName] = {};
|
||||
|
||||
sys_stats[stats[i].shortName].callTotals = stats[i].callTotals;
|
||||
sys_stats[stats[i].shortName].errorTotals = stats[i].errorTotals;
|
||||
sys_stats[stats[i].shortName].errorStats = stats[i].errorStats;
|
||||
sys_stats[stats[i].shortName].usageBytes = stats[i].usageBytes;
|
||||
sys_stats[stats[i].shortName].usageMb = stats[i].usageMb;
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
systems: returnSys,
|
||||
stats: sys_stats
|
||||
});
|
||||
return;
|
||||
}
|
||||
);
|
||||
let returnSys = systems.map(obj => {
|
||||
var rObj = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}))(obj);
|
||||
if (obj.showScreenName) {
|
||||
rObj.screenName = req.user.screenName;
|
||||
} else {
|
||||
rObj.screenName = null;
|
||||
}
|
||||
);
|
||||
rObj.id = obj._id;
|
||||
return rObj;
|
||||
});
|
||||
|
||||
const stats = await SystemStat.find({ shortName: { $in: shortNames } }).exec();
|
||||
var sys_stats = {}
|
||||
for (var i = 0; i < stats.length; i++) {
|
||||
sys_stats[stats[i].shortName] = {};
|
||||
|
||||
sys_stats[stats[i].shortName].callTotals = stats[i].callTotals;
|
||||
sys_stats[stats[i].shortName].errorTotals = stats[i].errorTotals;
|
||||
sys_stats[stats[i].shortName].errorStats = stats[i].errorStats;
|
||||
sys_stats[stats[i].shortName].usageBytes = stats[i].usageBytes;
|
||||
sys_stats[stats[i].shortName].usageMb = stats[i].usageMb;
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
systems: returnSys,
|
||||
stats: sys_stats
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
function remove_system(shortName) {
|
||||
/*
|
||||
var sysDir = config.mediaDirectory + "/" + shortName + "/"
|
||||
console.log("Removing DIR: " + sysDir);
|
||||
fs.remove(sysDir, function(err) {
|
||||
if (err) return console.error(err)
|
||||
});
|
||||
const remove_system = async (shortName) => {
|
||||
|
||||
Transmission.remove({
|
||||
shortName: shortName
|
||||
}, function(err) {
|
||||
if (err) return console.error(err);
|
||||
// removed!
|
||||
});
|
||||
*/
|
||||
System.remove({
|
||||
shortName: shortName
|
||||
},
|
||||
function (err) {
|
||||
if (err) return console.error(err);
|
||||
// removed!
|
||||
}
|
||||
);
|
||||
await System.deleteOne({
|
||||
shortName: shortName
|
||||
}).catch(err => console.error(err));
|
||||
|
||||
Talkgroup.remove({
|
||||
shortName: shortName
|
||||
},
|
||||
function (err) {
|
||||
if (err) return console.error(err);
|
||||
// removed!
|
||||
}
|
||||
);
|
||||
await Talkgroup.deleteMany({
|
||||
shortName: shortName
|
||||
}).catch(err => console.error(err));
|
||||
|
||||
Group.remove({
|
||||
shortName: shortName
|
||||
},
|
||||
function (err) {
|
||||
if (err) return console.error(err);
|
||||
// removed!
|
||||
}
|
||||
);
|
||||
|
||||
await Group.deleteMany({
|
||||
shortName: shortName
|
||||
}).catch(err => console.error(err));
|
||||
}
|
||||
|
||||
exports.deleteSystem = function (req, res, next) {
|
||||
exports.deleteSystem = async function (req, res, next) {
|
||||
//router.get('/delete_system/:shortName', isLoggedIn, function(req, res) {
|
||||
System.findOne({
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function (err, system) {
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
console.log("Error deleting syste - Wrong User - sys: " + system.userId + " profile: " + mongoose.Types.ObjectId(req.user._id));
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log("Removing: " + system.name);
|
||||
remove_system(system.shortName);
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
);
|
||||
};
|
||||
var system = await System.findOne({
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
}).catch(err => {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
if (!system) {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
console.log("Error deleting syste - Wrong User - sys: " + system.userId + " profile: " + mongoose.Types.ObjectId(req.user._id));
|
||||
res.status(401);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log("Removing: " + system.name);
|
||||
remove_system(system.shortName);
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------
|
||||
exports.ownSystem = async function (req, res, next) {
|
||||
@@ -189,6 +149,7 @@ exports.ownSystem = async function (req, res, next) {
|
||||
|
||||
if (!system) {
|
||||
console.error("ERROR: OwnSystem - system does not exist: " + req.body.shortName.toLowerCase());
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That System does not exist."
|
||||
@@ -197,7 +158,7 @@ exports.ownSystem = async function (req, res, next) {
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
console.error("ERROR: OwnSystem - not users system " + req.body.shortName.toLowerCase());
|
||||
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
@@ -210,6 +171,7 @@ exports.ownSystem = async function (req, res, next) {
|
||||
|
||||
} catch (err) {
|
||||
console.error("ERROR: ownSystem - caught: " + err);
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
@@ -224,12 +186,13 @@ exports.uniqueShortName = async function (req, res, next) {
|
||||
|
||||
var shortName = res.locals.shortName;
|
||||
if (typeof shortName == 'undefined') {
|
||||
console.error("locals: " + res.locals.shortName + " body: " + req.body.shortName )
|
||||
console.error("locals: " + res.locals.shortName + " body: " + req.body.shortName)
|
||||
console.error("ShortName is not in res.local, pulling from req.body instead")
|
||||
shortName = req.body.shortName;
|
||||
}
|
||||
if (typeof shortName == 'undefined') {
|
||||
console.error("ShortName is not in req.body instead")
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Short Name not provided in form"
|
||||
@@ -243,6 +206,7 @@ exports.uniqueShortName = async function (req, res, next) {
|
||||
|
||||
if (system) {
|
||||
console.error("ERROR: Unique Shortname already in use: " + req.body.shortName.toLowerCase());
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Short Name already in use"
|
||||
@@ -255,10 +219,8 @@ exports.uniqueShortName = async function (req, res, next) {
|
||||
|
||||
} catch (err) {
|
||||
console.error("ERROR: Unique ShortName - caught: " + err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
res.status(500);
|
||||
res.json({ message: "ERROR: Unique ShortName - caught: " + err });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -279,59 +241,58 @@ exports.updateSystem = async function (req, res, next) {
|
||||
res.locals.system.showScreenName = res.locals.showScreenName;
|
||||
res.locals.system.ignoreUnknownTalkgroup = res.locals.ignoreUnknownTalkgroup;
|
||||
|
||||
res.locals.system.save(function (err) {
|
||||
if (err) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
var returnSys = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}))(res.locals.system);
|
||||
returnSys.id = res.locals.system._id;
|
||||
res.json({
|
||||
success: true,
|
||||
system: returnSys
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await res.locals.system.save().catch(err => {
|
||||
res.status(500);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
var returnSys = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
key,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}))(res.locals.system);
|
||||
returnSys.id = res.locals.system._id;
|
||||
res.json(returnSys);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
exports.validateSystem = async function (req, res, next) {
|
||||
try {
|
||||
res.locals.showScreenName = req.body.showScreenName;
|
||||
res.locals.ignoreUnknownTalkgroup = req.body.ignoreUnknownTalkgroup ;
|
||||
res.locals.ignoreUnknownTalkgroup = req.body.ignoreUnknownTalkgroup;
|
||||
if (!req.body.name || (req.body.name.length < 2)) {
|
||||
console.error("ERROR: Validate System - req.body.name");
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "System Name is Required"
|
||||
@@ -342,6 +303,7 @@ exports.validateSystem = async function (req, res, next) {
|
||||
|
||||
if (!req.body.description || (req.body.description.length < 2)) {
|
||||
console.error("ERROR: Validate System - req.body.description");
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "System Description is Required"
|
||||
@@ -352,6 +314,7 @@ exports.validateSystem = async function (req, res, next) {
|
||||
|
||||
if (!req.body.shortName || (req.body.shortName.length < 2)) {
|
||||
console.error("ERROR: Validate System - req.body.shortName");
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "System ShortName is Required"
|
||||
@@ -367,6 +330,7 @@ exports.validateSystem = async function (req, res, next) {
|
||||
// make sure it is one of the expected types
|
||||
if ((req.body.systemType != "state") && (req.body.systemType != "city") && (req.body.systemType != "county") && (req.body.systemType != "international")) {
|
||||
console.error("ERROR: Validate System - req.body.systemType: " + req.body.systemType);
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Select system location"
|
||||
@@ -383,6 +347,7 @@ exports.validateSystem = async function (req, res, next) {
|
||||
req.body.systemType === "county") {
|
||||
if (!req.body.state || (req.body.state === "") || (req.body.state.length < 2)) {
|
||||
console.error("ERROR: Validate System - req.body.state: " + req.body.state);
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Select the State for the System"
|
||||
@@ -395,6 +360,7 @@ exports.validateSystem = async function (req, res, next) {
|
||||
if (req.body.systemType === "city") {
|
||||
if (!req.body.city || (req.body.city === "") || (req.body.city.length < 2)) {
|
||||
console.error("ERROR: Validate System - req.body.systemType: " + req.body.systemType);
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Enter the city for the System"
|
||||
@@ -407,6 +373,7 @@ exports.validateSystem = async function (req, res, next) {
|
||||
if (req.body.systemType === "county") {
|
||||
if (!req.body.county || (req.body.county === "") || (req.body.county.length < 2)) {
|
||||
console.error("ERROR: Validate System - req.body.county: " + req.body.county);
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Enter the county for the System"
|
||||
@@ -419,6 +386,7 @@ exports.validateSystem = async function (req, res, next) {
|
||||
if (req.body.systemType === "international") {
|
||||
if (!req.body.country || (req.body.country === "") || (req.body.country.length < 2)) {
|
||||
console.error("ERROR: Validate System - req.body.country: " + req.body.country);
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Enter the country for the System"
|
||||
@@ -431,6 +399,7 @@ exports.validateSystem = async function (req, res, next) {
|
||||
|
||||
} catch (err) {
|
||||
console.error("ERROR: Validate System - caught: " + err);
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
@@ -457,7 +426,7 @@ exports.createSystem = async function (req, res, next) {
|
||||
county,
|
||||
country,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
ignoreUnknownTalkgroup
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
@@ -468,51 +437,50 @@ exports.createSystem = async function (req, res, next) {
|
||||
county,
|
||||
country,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
ignoreUnknownTalkgroup
|
||||
}))(res.locals);
|
||||
system.key = key;
|
||||
system.userId = mongoose.Types.ObjectId(req.user._id);
|
||||
System.create(system, function (err, newSys) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
}
|
||||
var returnSys = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup ,
|
||||
key
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup ,
|
||||
key
|
||||
}))(newSys);
|
||||
returnSys.id = newSys._id;
|
||||
system.userId = new mongoose.Types.ObjectId(req.user._id);
|
||||
const newSys = await System.create(system).catch(err => {
|
||||
|
||||
console.error(err);
|
||||
res.status(500)
|
||||
res.json({
|
||||
success: true,
|
||||
system: returnSys
|
||||
success: false,
|
||||
message: err
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
var returnSys = (({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup,
|
||||
key
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
userId,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup,
|
||||
key
|
||||
}))(newSys);
|
||||
returnSys.id = newSys._id;
|
||||
res.json(returnSys);
|
||||
return;
|
||||
|
||||
}
|
||||
@@ -1,255 +1,220 @@
|
||||
const Talkgroup = require("../models/talkgroup");
|
||||
const System = require("../models/system");
|
||||
const fs = require('fs-extra');
|
||||
const {parse} = require('csv-parse');
|
||||
|
||||
const crypto = require("crypto");
|
||||
var parse = require('csv-parse');
|
||||
|
||||
exports.isLoggedIn = function(req, res, next) {
|
||||
exports.isLoggedIn = function (req, res, next) {
|
||||
if (req.isAuthenticated()) return next();
|
||||
res.redirect(account_server + "/login");
|
||||
};
|
||||
|
||||
//router.get("/tg_export/:shortName", isLoggedIn, function(req, res) {
|
||||
exports.exportTalkgroups = function(req, res, next) {
|
||||
process.nextTick(function() {
|
||||
System.findOne(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function(err, system) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
if (!system) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
Talkgroup.find(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function(err, talkgroups) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
if (!talkgroups || !Array.isArray(talkgroups)) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Talkgroups were not found."
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.setHeader("Content-Type", "text/csv");
|
||||
talkgroups.forEach(function(item) {
|
||||
/* res.write('"' + item.num + '", "'
|
||||
+ item.mode + '", "'
|
||||
+ item.alpha + '", "'
|
||||
+ item.description + '", "'
|
||||
+ item.tag + '", "'
|
||||
+ item.group + '", "'
|
||||
+ item.priority+ '"\r\n');*/
|
||||
if (item) {
|
||||
var sending = "";
|
||||
sending = item.num + ", ";
|
||||
if (item.mode) {
|
||||
sending = sending + item.mode.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.alpa) {
|
||||
sending = sending + item.alpha.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.description) {
|
||||
sending = sending + item.description.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.tag) {
|
||||
sending = sending + item.tag.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.group) {
|
||||
sending = sending + item.group.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.priority) {
|
||||
sending = sending + item.priority.toString().replace(/\,/g, "") + "\r\n";
|
||||
} else {
|
||||
sending = sending + "\r\n";
|
||||
}
|
||||
res.write(sending);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
res.end();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
function csv_import(shortName, filename, callback) {
|
||||
var talkgroups = [];
|
||||
Talkgroup.remove(
|
||||
{
|
||||
shortName: shortName
|
||||
},
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.error("Error: csv import: " + err);
|
||||
callback(err, null);
|
||||
} else {
|
||||
var parser = parse(
|
||||
{
|
||||
columns: [
|
||||
"num",
|
||||
"mode",
|
||||
"alpha",
|
||||
"description",
|
||||
"tag",
|
||||
"group",
|
||||
"priority"
|
||||
]
|
||||
},
|
||||
async function(err, data) {
|
||||
if (err) {
|
||||
console.error("Error: csv import 2: " + err);
|
||||
callback(err, null);
|
||||
} else {
|
||||
var response = [];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var row = data[i];
|
||||
row.num = parseInt(row.num);
|
||||
row.tag = row.tag.toLowerCase();
|
||||
row.group = row.group.toLowerCase();
|
||||
var newTg = new Talkgroup();
|
||||
newTg.num = row.num;
|
||||
newTg.alpha = row.alpha;
|
||||
newTg.mode = row.mode;
|
||||
newTg.description = row.description;
|
||||
newTg.tag = row.tag;
|
||||
newTg.group = row.group;
|
||||
newTg.priority = row.priority;
|
||||
newTg.shortName = shortName;
|
||||
|
||||
await newTg.save(function(err) {
|
||||
if (err) {
|
||||
console.log("Error: CSV import: " + err);
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
|
||||
response.push(row);
|
||||
}
|
||||
callback(null, response);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
fs.createReadStream(filename).pipe(parser);
|
||||
// removed!
|
||||
exports.exportTalkgroups = function (req, res, next) {
|
||||
process.nextTick(async function () {
|
||||
const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
|
||||
if (!system) {
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
res.status(401);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
const talkgroups = await Talkgroup.find({ shortName: req.params.shortName.toLowerCase() })
|
||||
|
||||
if (!talkgroups || !Array.isArray(talkgroups)) {
|
||||
console.error(err);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "Talkgroups were not found."
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.setHeader("Content-Type", "text/csv");
|
||||
talkgroups.forEach(function (item) {
|
||||
/* res.write('"' + item.num + '", "'
|
||||
+ item.mode + '", "'
|
||||
+ item.alpha + '", "'
|
||||
+ item.description + '", "'
|
||||
+ item.tag + '", "'
|
||||
+ item.group + '", "'
|
||||
+ item.priority+ '"\r\n');*/
|
||||
if (item) {
|
||||
var sending = "";
|
||||
sending = item.num + ", ";
|
||||
if (item.mode) {
|
||||
sending = sending + item.mode.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.alpa) {
|
||||
sending = sending + item.alpha.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.description) {
|
||||
sending = sending + item.description.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.tag) {
|
||||
sending = sending + item.tag.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.group) {
|
||||
sending = sending + item.group.toString().replace(/\,/g, "") + ", ";
|
||||
} else {
|
||||
sending = sending + " ,";
|
||||
}
|
||||
if (item.priority) {
|
||||
sending = sending + item.priority.toString().replace(/\,/g, "") + "\r\n";
|
||||
} else {
|
||||
sending = sending + "\r\n";
|
||||
}
|
||||
res.write(sending);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
res.end();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//router.post("/tg_import/:shortName", isLoggedIn, upload.single('file'), function(req, res, next) {
|
||||
exports.importTalkgroups = function(req, res, next) {
|
||||
process.nextTick(function() {
|
||||
System.findOne(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function(err, system) {
|
||||
if (err) {
|
||||
console.error("Error - importTalkgroups: " + err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
if (!system) {
|
||||
console.error("Error - importTalkgroups: System not found - " + req.params.shortName.toLowerCase());
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
console.error("Error - importTalkgroups: you are not the user for hte system: " + req.user._id);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!req.file || req.file.size === 0) {
|
||||
res.json({ success: false, message: "Please select a file." });
|
||||
return;
|
||||
}
|
||||
async function csv_import(shortName, filename, callback) {
|
||||
var talkgroups = [];
|
||||
const count = await Talkgroup.deleteMany({ shortName: shortName });
|
||||
|
||||
fs.exists(req.file.path, function(exists) {
|
||||
if (exists) {
|
||||
csv_import(system.shortName, req.file.path, function(err, data) {
|
||||
if (err) {
|
||||
console.error("Erorr - CSV import, callback: " + err);
|
||||
res.json({ success: false, message: err });
|
||||
res.end();
|
||||
return;
|
||||
} else {
|
||||
res.json({ success: true, talkgroups: data });
|
||||
return;
|
||||
}
|
||||
});
|
||||
var parser = parse(
|
||||
{
|
||||
columns: [
|
||||
"num",
|
||||
"mode",
|
||||
"alpha",
|
||||
"description",
|
||||
"tag",
|
||||
"group",
|
||||
"priority"
|
||||
]
|
||||
},
|
||||
async function (err, data) {
|
||||
if (err) {
|
||||
console.error("Error: csv import 2: " + err);
|
||||
callback(err, null);
|
||||
} else {
|
||||
var response = [];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var row = data[i];
|
||||
row.num = parseInt(row.num);
|
||||
row.tag = row.tag.toLowerCase();
|
||||
row.group = row.group.toLowerCase();
|
||||
var newTg = new Talkgroup();
|
||||
newTg.num = row.num;
|
||||
newTg.alpha = row.alpha;
|
||||
newTg.mode = row.mode;
|
||||
newTg.description = row.description;
|
||||
newTg.tag = row.tag;
|
||||
newTg.group = row.group;
|
||||
newTg.priority = row.priority;
|
||||
newTg.shortName = shortName;
|
||||
|
||||
await newTg.save().catch(err => {
|
||||
console.log("Error: CSV import: " + err);
|
||||
callback(err, null);
|
||||
});
|
||||
|
||||
response.push(row);
|
||||
}
|
||||
callback(null, response);
|
||||
}
|
||||
}
|
||||
);
|
||||
fs.createReadStream(filename).pipe(parser);
|
||||
}
|
||||
|
||||
|
||||
//router.post("/tg_import/:shortName", isLoggedIn, upload.single('file'), function(req, res, next) {
|
||||
exports.importTalkgroups = function (req, res, next) {
|
||||
process.nextTick(async function () {
|
||||
const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() }).catch(err => {
|
||||
res.status(500);
|
||||
console.error("Error - importTalkgroups: " + err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
});
|
||||
|
||||
if (!system) {
|
||||
console.error("Error - importTalkgroups: System not found - " + req.params.shortName.toLowerCase());
|
||||
res.status(404);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "That Short Name does not exist."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!system.userId.equals(req.user._id)) {
|
||||
console.error("Error - importTalkgroups: you are not the user for hte system: " + req.user._id);
|
||||
res.status(401);
|
||||
res.json({
|
||||
success: false,
|
||||
message: "You are not the user associated with this system."
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!req.file || req.file.size === 0) {
|
||||
console.error("Error - File is bad. Size");
|
||||
res.status(500);
|
||||
res.json({ success: false, message: "Please select a file." });
|
||||
return;
|
||||
}
|
||||
|
||||
fs.exists(req.file.path, function (exists) {
|
||||
if (exists) {
|
||||
csv_import(system.shortName, req.file.path, function (err, data) {
|
||||
if (err) {
|
||||
console.error("Erorr - CSV import, callback: " + err);
|
||||
res.status(500);
|
||||
res.json({ success: false, message: err });
|
||||
res.end();
|
||||
return;
|
||||
} else {
|
||||
console.error("Error - importTalkgroups: no file");
|
||||
res.end(
|
||||
"Well, there is no magic for those who don’t believe in it!"
|
||||
);
|
||||
res.json( data );
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(500);
|
||||
console.error("Error - importTalkgroups: no file");
|
||||
res.end(
|
||||
"Well, there is no magic for those who don’t believe in it!"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
exports.fetchTalkgroups = function(req, res, next) {
|
||||
Talkgroup.find(
|
||||
{
|
||||
shortName: req.params.shortName.toLowerCase()
|
||||
},
|
||||
function(err, talkgroups) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
}
|
||||
res.json({ success: true, talkgroups: talkgroups });
|
||||
return;
|
||||
}
|
||||
);
|
||||
};
|
||||
exports.fetchTalkgroups = async function (req, res, next) {
|
||||
const talkgroups = await Talkgroup.find({ shortName: req.params.shortName.toLowerCase() }).catch(err => {
|
||||
console.error(err);
|
||||
res.status(500);
|
||||
res.json({ success: false, message: err });
|
||||
return;
|
||||
});
|
||||
|
||||
res.json(talkgroups );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const configureExpress = require("./config/express");
|
||||
const systems = require("./controllers/systems");
|
||||
const groups = require("./controllers/groups");
|
||||
const talkgroups = require("./controllers/talkgroups");
|
||||
const Permission = require("./models/permission");
|
||||
|
||||
require("./models/user");
|
||||
const multer = require('multer');
|
||||
|
||||
@@ -42,13 +42,7 @@ async function connect() {
|
||||
|
||||
// Connect to a MongoDB server running on 'localhost:27017' and use the
|
||||
// 'test' database.
|
||||
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true }, (err, res) => {
|
||||
if (err) {
|
||||
console.log(`Mongoose - Error connecting to ${secrets.db}. ${err}`)
|
||||
} else {
|
||||
console.log(`Mongoose Successfully connected to ${secrets.db}.`)
|
||||
}
|
||||
})
|
||||
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true });
|
||||
console.log("All Done");
|
||||
}
|
||||
connect();
|
||||
@@ -66,26 +60,6 @@ mongoose.connection.on('disconnected', () => {
|
||||
const isDev = process.env.NODE_ENV === "development"
|
||||
|
||||
|
||||
/*
|
||||
async function getRole(req) {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
if (req.params.shortName && req.user) {
|
||||
var short_name = req.params.shortName.toLowerCase();
|
||||
var userId = req.user._id;
|
||||
Permission.findOne({'userId': userId, 'shortName': short_name}, function(err, permission) {
|
||||
if (err) {
|
||||
console.error('Error - getRole userId: ' + userId + ' shortName: ' + short_name + ' error: ' + err);
|
||||
reject(-1);
|
||||
}
|
||||
console.log('Found - getRole userId: ' + userId + ' shortName: ' + short_name + ' role: ' + permission.role);
|
||||
|
||||
resolve(permission.role);
|
||||
});
|
||||
} else {
|
||||
reject(-1);
|
||||
}});
|
||||
return promise;
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
@@ -141,6 +115,22 @@ function isLoggedIn(req, res, next) {
|
||||
});
|
||||
};
|
||||
|
||||
function uploadFile(req, res, next) {
|
||||
const upload = multer({dest: 'uploads/'}).single('file');
|
||||
|
||||
upload(req, res, function (err) {
|
||||
if (err instanceof multer.MulterError) {
|
||||
console.error("Multer Error: " + err)
|
||||
} else if (err) {
|
||||
console.error(err);
|
||||
// An unknown error occurred when uploading.
|
||||
}
|
||||
console.log("Things look good for Multer")
|
||||
// Everything went fine.
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
configurePassport(app, passport)
|
||||
@@ -150,13 +140,8 @@ configureExpress(app, passport)
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.get("")
|
||||
|
||||
//app.get("/permissions/:shortname", isOwner, permissions.fetchPermissions)
|
||||
/*app.get("/permissions/:shortName", permissions.fetchPermissions);
|
||||
app.delete("/permissions/:shortName/:permissionId", isAdmin, permissions.deletePermission);
|
||||
app.post("/permissions/:shortName/:permissionId", isAdmin, permissions.updatePermission);
|
||||
app.post("/permissions/:shortName", isAdmin, permissions.addPermission);*/
|
||||
app.get("/talkgroups/:shortName", isLoggedIn, talkgroups.fetchTalkgroups)
|
||||
app.post("/talkgroups/:shortName/import", isLoggedIn, upload.single('file'), talkgroups.importTalkgroups)
|
||||
app.post("/talkgroups/:shortName/import", isLoggedIn, uploadFile, talkgroups.importTalkgroups)
|
||||
app.get("/talkgroups/:shortName/export", isLoggedIn, talkgroups.exportTalkgroups)
|
||||
app.post("/groups/:shortName/reorder", isLoggedIn, groups.reorderGroups);
|
||||
app.post("/groups/:shortName/:groupId?", isLoggedIn, groups.upsertGroup);
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Defining a User Model in mongoose
|
||||
// Code modified from https://github.com/sahat/hackathon-starter
|
||||
|
||||
var mongoose = require("mongoose");
|
||||
|
||||
|
||||
|
||||
const PermissionSchema = new mongoose.Schema({
|
||||
userId: mongoose.Schema.Types.ObjectId,
|
||||
systemId: mongoose.Schema.Types.ObjectId,
|
||||
shortName: String,
|
||||
role: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
ver: {
|
||||
type: Number,
|
||||
default: 1.2
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
module.exports=mongoose.model("Permission", PermissionSchema)
|
||||
@@ -84,4 +84,4 @@ UserSchema.pre("save", function(next) {
|
||||
*/
|
||||
UserSchema.statics = {}
|
||||
|
||||
module.exports = mongoose.model("User", UserSchema)
|
||||
module.exports = mongoose.model("User", UserSchema)
|
||||
@@ -1,15 +1,30 @@
|
||||
import React, { Component } from "react"
|
||||
import NavigationContainer from "./NavigationContainer"
|
||||
import Navigation from "./Navigation"
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return(
|
||||
import Restricted from "./Restricted"
|
||||
|
||||
import About from "../Components/About"
|
||||
import System from "../System/System"
|
||||
import ListSystems from "../System/ListSystems"
|
||||
import UpdateSystem from "../System/UpdateSystem"
|
||||
import CreateSystem from "../System/CreateSystem"
|
||||
|
||||
|
||||
const App = (params) => {
|
||||
return (
|
||||
<div>
|
||||
<NavigationContainer />
|
||||
{this.props.children}
|
||||
<Navigation/>
|
||||
<Routes>
|
||||
<Route path="/about" element={ <Restricted><About/></Restricted>} />
|
||||
<Route path="/system/:shortName" element={ <Restricted><System/></Restricted>} />
|
||||
<Route path="/update-system/:shortName" element={ <Restricted><UpdateSystem/></Restricted>} />
|
||||
<Route path="/create-system" element={ <Restricted><CreateSystem/></Restricted>} />
|
||||
<Route path="/list-systems" element={ <Restricted><ListSystems/></Restricted>} />
|
||||
<Route path="/" element={ <Restricted><ListSystems/></Restricted>} />
|
||||
<Route render={() => (<div>Miss</div>)} />
|
||||
</Routes>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default App
|
||||
|
||||
@@ -1,55 +1,46 @@
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Menu, Dropdown } from "semantic-ui-react";
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { logoutUser } from "../features/user/userSlice";
|
||||
|
||||
const navStyle = {
|
||||
marginBottom: "30px"
|
||||
};
|
||||
const blankStyle = {
|
||||
marginBottom: "50px"
|
||||
};
|
||||
|
||||
class Navigation extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._logout = this._logout.bind(this);
|
||||
}
|
||||
const Navigation = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
const { email } = useSelector((state) => state.user);
|
||||
|
||||
_logout = event => {
|
||||
const logout = event => {
|
||||
event.preventDefault();
|
||||
this.props.manualLogout();
|
||||
dispatch(logoutUser({}));
|
||||
};
|
||||
|
||||
render() {
|
||||
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
|
||||
return (
|
||||
<div>
|
||||
{this.props.user.authenticated ? (
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
{process.env.REACT_APP_SITE_NAME}
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
<Link to="/list-systems">
|
||||
<Menu.Item name="systems"> Systems</Menu.Item>
|
||||
</Link>
|
||||
<Link to="/about">
|
||||
<Menu.Item name="about">About</Menu.Item>
|
||||
</Link>
|
||||
<Dropdown item text={this.props.user.email}>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
|
||||
<Dropdown.Item onClick={this._logout}>Logout</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Menu.Menu>
|
||||
</Menu>
|
||||
) : (
|
||||
<div style={blankStyle} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
|
||||
return (
|
||||
<div>
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
{process.env.REACT_APP_SITE_NAME}
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
<Link to="/list-systems">
|
||||
<Menu.Item name="systems"> Systems</Menu.Item>
|
||||
</Link>
|
||||
<Link to="/about">
|
||||
<Menu.Item name="about">About</Menu.Item>
|
||||
</Link>
|
||||
<Dropdown item text={ email }>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
|
||||
<Dropdown.Item onClick={logout}>Logout</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Menu.Menu>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Navigation;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as userActions from "../User/user-actions"
|
||||
import Navigation from "./Navigation"
|
||||
|
||||
|
||||
// Function passed in to `connect` to subscribe to Redux store updates.
|
||||
// Any time it updates, mapStateToProps is called.
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Connects React component to the redux store
|
||||
// It does not modify the component class passed to it
|
||||
// Instead, it returns a new, connected component class, for you to use.
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
userActions
|
||||
)(Navigation)
|
||||
@@ -1,54 +1,29 @@
|
||||
// in src/restricted.js
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import * as types from "../User/user-constants";
|
||||
import axios from "axios";
|
||||
import React, { useEffect} from "react";
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { authenticateUser } from "../features/user/userSlice";
|
||||
|
||||
/**
|
||||
* Higher-order component (HOC) to wrap restricted pages
|
||||
*/
|
||||
const restricted = (BaseComponent, store) => {
|
||||
class Restricted extends Component {
|
||||
componentDidMount() {
|
||||
this.checkAuthentication(this.props);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.location !== this.props.location) {
|
||||
this.checkAuthentication(this.props);
|
||||
}
|
||||
}
|
||||
const Restricted = ({children }) => {
|
||||
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(authenticateUser({}));
|
||||
},[]);
|
||||
|
||||
checkAuthentication(params) {
|
||||
const authenticated = store.getState().user.authenticated;
|
||||
if (!authenticated) {
|
||||
axios
|
||||
.get(process.env.REACT_APP_ACCOUNT_SERVER + "/authenticated", { withCredentials: true })
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const user = response.data.user
|
||||
store.dispatch({
|
||||
type: types.LOGIN_SUCCESS_USER,
|
||||
data: user
|
||||
});
|
||||
} else {
|
||||
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
|
||||
/*history.replace({ pathname: '/login',
|
||||
state: { nextPathname: params.location.pathname } });*/
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
|
||||
// Something happened during logout that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return <BaseComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
return withRouter(Restricted);
|
||||
if (hasAuthenticated) {
|
||||
if (authenticated) {
|
||||
return children;;
|
||||
}
|
||||
else {
|
||||
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
|
||||
}
|
||||
}
|
||||
export default restricted
|
||||
return <div/>
|
||||
|
||||
};
|
||||
|
||||
export default Restricted
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { Component } from "react";
|
||||
import { Container, Header } from "semantic-ui-react";
|
||||
|
||||
class About extends Component {
|
||||
const About = () => {
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container text>
|
||||
<Header size='large'>Share the Air</Header>
|
||||
@@ -18,7 +17,7 @@ class About extends Component {
|
||||
<p> - Luke <a href="mailto:{process.env.REACT_APP_ADMIN_EMAIL}?Subject={process.env.REACT_APP_SITE_NAME}" target="_top">{process.env.REACT_APP_ADMIN_EMAIL}</a> </p>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default About;
|
||||
|
||||
@@ -1,31 +1,21 @@
|
||||
import React, { Component } from "react";
|
||||
import React from "react";
|
||||
import { Modal, Icon, Button, Header } from "semantic-ui-react";
|
||||
|
||||
class Message extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
const Message = (props) => {
|
||||
|
||||
_logout = event => {
|
||||
event.preventDefault();
|
||||
this.props.manualLogout();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal open={this.props.open} onClose={this.props.onClose} basic size="small">
|
||||
<Header icon="info" content={this.props.title} />
|
||||
<Modal.Content>
|
||||
<p>{this.props.message}</p>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button color='green' onClick={this.props.onClose} inverted>
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose} basic size="small">
|
||||
<Header icon="info" content={props.title} />
|
||||
<Modal.Content>
|
||||
<p>{props.message}</p>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button color='green' onClick={props.onClose} inverted>
|
||||
<Icon name='checkmark' /> Got it
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Message;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { Component } from "react";
|
||||
import React, { useEffect, useState, } from "react";
|
||||
import { useGetTalkgroupsQuery, useGetGroupsQuery, useUpdateGroupMutation, useCreateGroupMutation} from '../features/api/apiSlice'
|
||||
|
||||
import {
|
||||
Grid,
|
||||
Modal,
|
||||
@@ -13,84 +15,75 @@ import {
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
class GroupModal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
const GroupModal = (props) => {
|
||||
|
||||
// Set initial state
|
||||
this.state = {
|
||||
editGroup: false,
|
||||
groupName: "",
|
||||
group: {
|
||||
name: "",
|
||||
items: []
|
||||
},
|
||||
talkgroups: this.props.talkgroups ? this.props.talkgroups : []
|
||||
};
|
||||
const emptyGroup = {
|
||||
name: "",
|
||||
items: []
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
//this.props.groupActions.fetchGroups(this.props.shortName);
|
||||
const [groupName, setGroupName] = useState("");
|
||||
const [group, setGroup] = useState(emptyGroup)
|
||||
const [talkgroups, setTalkgroups] = useState(props.talkgroups ? props.talkgroups : [])
|
||||
|
||||
this.props.talkgroupActions.fetchTalkgroups(this.props.shortName);
|
||||
}
|
||||
const { data: talkgroupsData} = useGetTalkgroupsQuery(props.shortName);
|
||||
const { data: groupsData} = useGetGroupsQuery(props.shortName);
|
||||
const [ updateGroup] = useUpdateGroupMutation();
|
||||
const [ createGroup] = useCreateGroupMutation();
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
useEffect(() => {
|
||||
if (talkgroupsData && groupsData && props.editGroupId) {
|
||||
let editGroup = groupsData.find(
|
||||
group => group._id === props.editGroupId
|
||||
);
|
||||
let items = [];
|
||||
|
||||
// This determines if you are trying to edit a group.
|
||||
// In this case, the value of editGroupID will chane to the group being edited
|
||||
if (this.props.talkgroups && this.props.groups && this.props.editGroupId && !prevProps.editGroupId) {
|
||||
var editGroup = this.props.groups.find(
|
||||
group => group._id === this.props.editGroupId
|
||||
);
|
||||
var items = [];
|
||||
|
||||
var talkgroups = this.props.talkgroups;
|
||||
for (var i = 0; i < editGroup.talkgroups.length; i++) {
|
||||
const talkgroupId = editGroup.talkgroups[i];
|
||||
const index = talkgroups.findIndex(tg => tg.num === talkgroupId);
|
||||
if (index >= 0) {
|
||||
const item = {
|
||||
num: talkgroups[index].num,
|
||||
description: talkgroups[index].description
|
||||
};
|
||||
items.push(item);
|
||||
talkgroups = talkgroups
|
||||
.slice(0, index)
|
||||
.concat(talkgroups.slice(index + 1));
|
||||
}
|
||||
let talkgroups = talkgroupsData.slice(0); //copy
|
||||
for (var i = 0; i < editGroup.talkgroups.length; i++) {
|
||||
const talkgroupNum = editGroup.talkgroups[i];
|
||||
const index = talkgroups.findIndex(tg => tg.num === talkgroupNum);
|
||||
if (index >= 0) {
|
||||
const item = {
|
||||
num: talkgroups[index].num,
|
||||
description: talkgroups[index].description
|
||||
};
|
||||
items.push(item);
|
||||
talkgroups = talkgroups
|
||||
.slice(0, index)
|
||||
.concat(talkgroups.slice(index + 1));
|
||||
}
|
||||
// we want to remove that are part of the Group from the list on the Left
|
||||
const group = { name: editGroup.groupName, items: items }
|
||||
this.setState({ group: group, talkgroups: talkgroups, groupName: editGroup.groupName });
|
||||
}
|
||||
|
||||
if (this.props.talkgroups && !prevProps.talkgroups) {
|
||||
// If we are not editing, make sure there is the full list on the left.
|
||||
this.setState({ talkgroups: [...this.props.talkgroups] })
|
||||
// we want to remove that are part of the Group from the list on the Left
|
||||
const group = { name: editGroup.groupName, items: items }
|
||||
setGroup(group);
|
||||
setTalkgroups(talkgroups);
|
||||
setGroupName(editGroup.groupName);
|
||||
}
|
||||
},[groupsData,talkgroupsData, props.editGroupId]);
|
||||
|
||||
// If we are done editing, clear out the modal
|
||||
if (!this.props.editGroupId && (this.props.editGroupId !== prevProps.editGroupId)) {
|
||||
this.setState({
|
||||
groupName: "", group: {
|
||||
name: "",
|
||||
items: []
|
||||
},
|
||||
talkgroups: [...this.props.talkgroups]
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.editGroupId && talkgroupsData) {
|
||||
setTalkgroups([...talkgroupsData]);
|
||||
}
|
||||
}
|
||||
},[talkgroupsData, props.editGroupId]);
|
||||
|
||||
removeTalkgroup(event, index) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.editGroupId && props.talkgroups) {
|
||||
setGroupName("");
|
||||
setGroup(emptyGroup);
|
||||
setTalkgroups( [...props.talkgroups]);
|
||||
}
|
||||
},[props.editGroupId, props.talkgroups]);
|
||||
|
||||
const removeTalkgroup = (index) => {
|
||||
const item = {
|
||||
num: this.state.group.items[index].num,
|
||||
description: this.state.group.items[index].description
|
||||
num: group.items[index].num,
|
||||
description: group.items[index].description
|
||||
};
|
||||
|
||||
const talkgroups = this.state.talkgroups.slice(0); // copy
|
||||
let talkgroups = talkgroupsData.slice(0); // copy
|
||||
|
||||
for (var i = 0, len = talkgroups.length; i < len; i++) {
|
||||
if (item.num < talkgroups[i].num) {
|
||||
@@ -98,110 +91,75 @@ class GroupModal extends Component {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
group: {
|
||||
items: this.state.group.items
|
||||
.slice(0, index)
|
||||
.concat(this.state.group.items.slice(index + 1))
|
||||
},
|
||||
talkgroups: talkgroups
|
||||
setGroup( {
|
||||
items: this.state.group.items
|
||||
.slice(0, index)
|
||||
.concat(this.state.group.items.slice(index + 1))
|
||||
});
|
||||
setTalkgroups(talkgroups);
|
||||
}
|
||||
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
|
||||
addTalkgroup(event, index) {
|
||||
const addTalkgroup = (index) => {
|
||||
const item = {
|
||||
num: this.state.talkgroups[index].num,
|
||||
description: this.state.talkgroups[index].description
|
||||
num: talkgroupsData[index].num,
|
||||
description: talkgroupsData[index].description
|
||||
};
|
||||
|
||||
this.setState({
|
||||
group: { items: this.state.group.items.concat(item) },
|
||||
talkgroups: this.state.talkgroups
|
||||
.slice(0, index)
|
||||
.concat(this.state.talkgroups.slice(index + 1))
|
||||
});
|
||||
setGroup({ items: group.items.concat(item) })
|
||||
setTalkgroups(talkgroups.slice(0, index).concat(talkgroups.slice(index + 1)));
|
||||
}
|
||||
handleClose = () => {
|
||||
this.setState({
|
||||
groupName: "", group: {
|
||||
name: "",
|
||||
items: []
|
||||
},
|
||||
talkgroups: [...this.props.talkgroups]
|
||||
});
|
||||
this.props.onClose(false);
|
||||
}
|
||||
handleSubmit() {
|
||||
var talkgroupNums = this.state.group.items.map(a => a.num);
|
||||
|
||||
if (this.props.editGroupId) {
|
||||
const handleClose = () => {
|
||||
setGroupName("");
|
||||
setGroup(emptyGroup);
|
||||
setTalkgroups([...talkgroupsData]);
|
||||
props.onClose();
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const talkgroupNums = group.items.map(a => a.num);
|
||||
|
||||
if (props.editGroupId) {
|
||||
const data = {
|
||||
_id: this.props.editGroupId,
|
||||
shortName: this.props.shortName,
|
||||
groupName: this.state.groupName,
|
||||
_id: props.editGroupId,
|
||||
shortName: props.shortName,
|
||||
groupName: groupName,
|
||||
talkgroups: JSON.stringify(talkgroupNums)
|
||||
};
|
||||
|
||||
this.setState({
|
||||
groupName: "", group: {
|
||||
name: "",
|
||||
items: []
|
||||
},
|
||||
talkgroups: [...this.props.talkgroups]
|
||||
});
|
||||
this.props.groupActions.updateGroup(data).then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
requestMessage
|
||||
});
|
||||
this.props.onClose(false);
|
||||
} else {
|
||||
this.props.onClose(true);
|
||||
}
|
||||
});
|
||||
setGroupName("");
|
||||
setGroup(emptyGroup);
|
||||
setTalkgroups([...talkgroupsData]);
|
||||
await updateGroup(data);
|
||||
props.onClose(true);
|
||||
} else {
|
||||
const data = {
|
||||
shortName: this.props.shortName,
|
||||
groupName: this.state.groupName,
|
||||
shortName: props.shortName,
|
||||
groupName: groupName,
|
||||
talkgroups: JSON.stringify(talkgroupNums)
|
||||
};
|
||||
|
||||
this.setState({
|
||||
groupName: "", group: {
|
||||
name: "",
|
||||
items: []
|
||||
},
|
||||
talkgroups: [...this.props.talkgroups]
|
||||
});
|
||||
setGroupName("");
|
||||
setGroup(emptyGroup);
|
||||
setTalkgroups([...talkgroupsData]);
|
||||
|
||||
await createGroup(data);
|
||||
props.onClose();
|
||||
|
||||
this.props.groupActions.createGroup(data).then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
requestMessage
|
||||
});
|
||||
this.props.onClose(false);
|
||||
} else {
|
||||
this.props.onClose(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const talkgroups = this.state.talkgroups;
|
||||
const group = this.state.group.items;
|
||||
|
||||
|
||||
const railStyle = {
|
||||
top: "15px",
|
||||
minHeight: "200px"
|
||||
};
|
||||
return (
|
||||
<Modal open={this.props.open} onClose={this.handleClose} centered={false}>
|
||||
<Modal open={props.open} onClose={handleClose} centered={false}>
|
||||
<Modal.Header>
|
||||
{this.props.editGroupId ? "Edit Group" : "Create Group"}
|
||||
{props.editGroupId ? "Edit Group" : "Create Group"}
|
||||
</Modal.Header>
|
||||
<Modal.Content scrolling>
|
||||
<Modal.Description>
|
||||
@@ -227,7 +185,7 @@ class GroupModal extends Component {
|
||||
<Icon
|
||||
link
|
||||
name="plus"
|
||||
onClick={e => this.addTalkgroup(e, i)}
|
||||
onClick={e => addTalkgroup(e, i)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
@@ -238,26 +196,26 @@ class GroupModal extends Component {
|
||||
<Segment style={railStyle}>
|
||||
<Input
|
||||
name="groupName"
|
||||
onChange={this.handleInputChange}
|
||||
onChange={e => setGroupName(e.target.value)}
|
||||
placeholder="Group Name"
|
||||
value={this.state.groupName}
|
||||
value={groupName}
|
||||
/>
|
||||
<List divided relaxed>
|
||||
{group.map((talkgroup, i) => (
|
||||
{group.items.map((talkgroup, i) => (
|
||||
<List.Item key={"Group-" + i}>
|
||||
<List.Icon
|
||||
name="remove"
|
||||
verticalAlign="middle"
|
||||
link={true}
|
||||
onClick={e => this.removeTalkgroup(e, i)}
|
||||
onClick={e => removeTalkgroup(e, i)}
|
||||
/>
|
||||
<List.Content>{talkgroup.description}</List.Content>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
<Button onClick={this.handleSubmit}>
|
||||
<Button onClick={handleSubmit}>
|
||||
<Icon name="checkmark" />{" "}
|
||||
{this.props.editGroupId ? "Update" : "Save"}
|
||||
{props.editGroupId ? "Update" : "Save"}
|
||||
</Button>
|
||||
</Segment>
|
||||
</Rail>
|
||||
@@ -266,14 +224,13 @@ class GroupModal extends Component {
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={this.handleSubmit}>
|
||||
<Button onClick={handleSubmit}>
|
||||
<Icon name="checkmark" />{" "}
|
||||
{this.props.editGroupId ? "Update" : "Save"}
|
||||
{props.editGroupId ? "Update" : "Save"}
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupModal;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as groupActionCreators from "./group-actions"
|
||||
import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions"
|
||||
import { bindActionCreators } from 'redux'
|
||||
import GroupModal from "./GroupModal"
|
||||
|
||||
//groups: state.group.items[props.match.params.shortName],
|
||||
function mapStateToProps(state,props) {
|
||||
return {
|
||||
talkgroups: state.talkgroup.items[props.shortName],
|
||||
groups: state.group.items[props.shortName],
|
||||
shortName: props.shortName
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
groupActions: bindActionCreators(groupActionCreators,dispatch),
|
||||
talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(GroupModal)
|
||||
@@ -1,15 +1,4 @@
|
||||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Link } from 'react-router-dom'
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Icon,
|
||||
Table
|
||||
} from "semantic-ui-react";
|
||||
@@ -19,18 +8,22 @@ import {
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
class ListGroups extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
const ListGroups = (props) => {
|
||||
|
||||
|
||||
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
|
||||
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
|
||||
render() {
|
||||
const groups = this.props.groups;
|
||||
|
||||
const groups = props.groups;
|
||||
let groupsDisplay = [];
|
||||
if (groups) {
|
||||
// if a group gets deleted, it will still be listed in the Order array for a little.
|
||||
for (const id of props.order) {
|
||||
const group = props.groups.find( group => group._id === id );
|
||||
if (group) {
|
||||
groupsDisplay.push(group)
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
||||
<Table >
|
||||
@@ -42,7 +35,8 @@ class ListGroups extends Component {
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{groups.map((group, i) => (
|
||||
{groupsDisplay.map((group, i) => (
|
||||
|
||||
<Table.Row key={ "Group-" + i}>
|
||||
<Table.Cell>{group.groupName}</Table.Cell>
|
||||
<Table.Cell>{group.talkgroups.length}</Table.Cell>
|
||||
@@ -50,23 +44,27 @@ class ListGroups extends Component {
|
||||
<Icon
|
||||
name="pencil alternate"
|
||||
link={true}
|
||||
onClick={e => this.props.editGroup(group._id)}
|
||||
onClick={e => props.editGroup(group._id)}
|
||||
/><Icon
|
||||
name="up arrow"
|
||||
link={true}
|
||||
onClick={e => this.props.reorderGroup(i, i-1)}
|
||||
link={i===0?false:true}
|
||||
disabled={i===0?true:false}
|
||||
onClick={e => props.reorderGroup(i, i-1)}
|
||||
/><Icon
|
||||
name="down arrow"
|
||||
link={true}
|
||||
onClick={e => this.props.reorderGroup(i, i+1)}
|
||||
link={i===groupsDisplay.length-1?false:true}
|
||||
disabled={i===groupsDisplay.length-1?true:false}
|
||||
onClick={e => props.reorderGroup(i, i+1)}
|
||||
/><Icon
|
||||
name="remove"
|
||||
link={true}
|
||||
onClick={e => this.props.deleteGroup(group._id)}
|
||||
onClick={e => props.deleteGroup(group._id)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
|
||||
)
|
||||
)}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
@@ -77,6 +75,5 @@ class ListGroups extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ListGroups;
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
import axios from "axios";
|
||||
//import { browserHistory } from "react-router"
|
||||
import * as types from "./group-constants";
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
// "Log In" action creators
|
||||
function beginCreateGroup() {
|
||||
return { type: types.CREATE_GROUP };
|
||||
}
|
||||
|
||||
function createGroupSuccess(data) {
|
||||
return {
|
||||
type: types.CREATE_SUCCESS_GROUP,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
function createGroupError(data) {
|
||||
return {
|
||||
type: types.CREATE_ERROR_GROUP,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function beginUpdateGroup() {
|
||||
return { type: types.UPDATE_GROUP };
|
||||
}
|
||||
|
||||
function updateGroupSuccess(data) {
|
||||
return { type: types.UPDATE_SUCCESS_GROUP, data };
|
||||
}
|
||||
|
||||
function updateGroupError() {
|
||||
return { type: types.UPDATE_ERROR_GROUP };
|
||||
}
|
||||
|
||||
|
||||
function beginDeleteGroup() {
|
||||
return { type: types.DELETE_GROUP };
|
||||
}
|
||||
|
||||
function deleteGroupSuccess(data) {
|
||||
return { type: types.DELETE_SUCCESS_GROUP, data };
|
||||
}
|
||||
|
||||
function deleteGroupError() {
|
||||
return { type: types.DELETE_ERROR_GROUP };
|
||||
}
|
||||
|
||||
function beginFetchGroup() {
|
||||
return { type: types.FETCH_GROUP };
|
||||
}
|
||||
|
||||
function fetchGroupSuccess(data) {
|
||||
return {
|
||||
type: types.FETCH_SUCCESS_GROUP,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
function fetchGroupError(data) {
|
||||
return {
|
||||
type: types.FETCH_ERROR_GROUP,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
function saveGroupOrderSuccess() {
|
||||
return { type: types.SAVE_ORDER_SUCCESS_GROUP };
|
||||
}
|
||||
|
||||
function saveGroupOrderError() {
|
||||
return { type: types.SAVE_ORDER_ERROR_GROUP };
|
||||
}
|
||||
|
||||
function makeUserRequest(method, data, api = "/login") {
|
||||
// returns a Promise
|
||||
return axios({
|
||||
method: method,
|
||||
url: api,
|
||||
data: data,
|
||||
withCredentials: true
|
||||
});
|
||||
}
|
||||
|
||||
export function changeUrl(url) {
|
||||
return dispatch => {
|
||||
dispatch(push(url));
|
||||
};
|
||||
}
|
||||
|
||||
// Example of an Async Action Creator
|
||||
// http://redux.js.org/docs/advanced/AsyncActions.html
|
||||
|
||||
export function updateGroup(data) {
|
||||
return dispatch => {
|
||||
dispatch(beginUpdateGroup());
|
||||
|
||||
return makeUserRequest("post", data, process.env.REACT_APP_ADMIN_SERVER + "/groups/" + data.shortName + "/" + data._id)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(updateGroupSuccess(response.data.group));
|
||||
} else {
|
||||
dispatch(updateGroupError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function shouldFetchGroups(state,shortName) {
|
||||
if (!state.group.items || !state.group.items[shortName]) {
|
||||
return true;
|
||||
} else if (state.group.isWaiting) {
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function reorderGroup(shortName, oldIndex, newIndex) {
|
||||
return (dispatch, getState) => {
|
||||
const data = {
|
||||
shortName: shortName,
|
||||
oldIndex: oldIndex,
|
||||
newIndex: newIndex
|
||||
};
|
||||
dispatch({
|
||||
type: types.REORDER_GROUP,
|
||||
data
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchGroups(shortName) {
|
||||
return (dispatch, getState) => {
|
||||
if (shouldFetchGroups(getState(), shortName)) {
|
||||
dispatch(beginFetchGroup());
|
||||
return axios
|
||||
.get(process.env.REACT_APP_ADMIN_SERVER + "/groups/" + shortName, {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const data = { shortName: shortName, groups: response.data.groups };
|
||||
dispatch(fetchGroupSuccess(data));
|
||||
} else {
|
||||
dispatch(fetchGroupError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function saveGroupOrder(shortName, data) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: types.SAVE_ORDER_GROUP,
|
||||
data
|
||||
});
|
||||
|
||||
return makeUserRequest(
|
||||
"post",
|
||||
data,
|
||||
process.env.REACT_APP_ADMIN_SERVER + "/groups/" + shortName + "/reorder"
|
||||
)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(saveGroupOrderSuccess());
|
||||
} else {
|
||||
dispatch(saveGroupOrderError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteGroup(shortName, groupId) {
|
||||
return dispatch => {
|
||||
dispatch(beginDeleteGroup());
|
||||
|
||||
return makeUserRequest("delete", null, process.env.REACT_APP_ADMIN_SERVER + "/groups/" + shortName + "/" + groupId)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const data = {shortName: shortName, groupId: groupId}
|
||||
dispatch(deleteGroupSuccess(data));
|
||||
} else {
|
||||
dispatch(deleteGroupError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function createGroup(data) {
|
||||
return dispatch => {
|
||||
dispatch(beginCreateGroup());
|
||||
|
||||
return makeUserRequest("post", data, process.env.REACT_APP_ADMIN_SERVER + "/groups/" + data.shortName)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(createGroupSuccess(response.data.group));
|
||||
} else {
|
||||
dispatch(createGroupError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
export const CREATE_GROUP = "CREATE_GROUP"
|
||||
export const CREATE_SUCCESS_GROUP = "CREATE_SUCCESS_GROUP"
|
||||
export const CREATE_ERROR_GROUP = "CREATE_ERROR_GROUP"
|
||||
export const UPDATE_GROUP = "UPDATE_GROUP"
|
||||
export const UPDATE_SUCCESS_GROUP = "UPDATE_SUCCESS_GROUP"
|
||||
export const UPDATE_ERROR_GROUP = "UPDATE_ERROR_GROUP"
|
||||
export const DELETE_GROUP = "DELETE_GROUP"
|
||||
export const DELETE_SUCCESS_GROUP = "DELETE_SUCCESS_GROUP"
|
||||
export const DELETE_ERROR_GROUP = "DELETE_ERROR_GROUP"
|
||||
export const FETCH_GROUP = "FETCH_GROUP"
|
||||
export const FETCH_SUCCESS_GROUP = "FETCH_SUCCESS_GROUP"
|
||||
export const FETCH_ERROR_GROUP = "FETCH_ERROR_GROUP"
|
||||
export const REORDER_GROUP = "REORDER_GROUP"
|
||||
export const SAVE_ORDER_GROUP = "SAVE_ORDER_GROUP"
|
||||
export const SAVE_ORDER_SUCCESS_GROUP = "SAVE_ORDER_SUCCESS_GROUP"
|
||||
export const SAVE_ORDER_ERROR_GROUP = "SAVE_ORDER_ERROR_GROUP"
|
||||
@@ -1,123 +0,0 @@
|
||||
import {
|
||||
CREATE_GROUP,
|
||||
CREATE_SUCCESS_GROUP,
|
||||
CREATE_ERROR_GROUP,
|
||||
DELETE_GROUP,
|
||||
DELETE_SUCCESS_GROUP,
|
||||
DELETE_ERROR_GROUP,
|
||||
UPDATE_GROUP,
|
||||
UPDATE_SUCCESS_GROUP,
|
||||
UPDATE_ERROR_GROUP,
|
||||
FETCH_GROUP,
|
||||
FETCH_SUCCESS_GROUP,
|
||||
FETCH_ERROR_GROUP,
|
||||
REORDER_GROUP,
|
||||
SAVE_ORDER_GROUP,
|
||||
SAVE_ORDER_ERROR_GROUP,
|
||||
SAVE_ORDER_SUCCESS_GROUP
|
||||
} from "./group-constants";
|
||||
|
||||
|
||||
function updateItemInArray(array, updatedItem) {
|
||||
const updatedItems = array.map(item => {
|
||||
if(item._id !== updatedItem._id) {
|
||||
// Since we only want to update one item, preserve all others as they are now
|
||||
return item;
|
||||
}
|
||||
return updatedItem;
|
||||
});
|
||||
|
||||
return updatedItems;
|
||||
}
|
||||
|
||||
const group = (
|
||||
state = {
|
||||
orderChange: false,
|
||||
isWaiting: false,
|
||||
items: {}},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
||||
case SAVE_ORDER_GROUP:
|
||||
case CREATE_GROUP:
|
||||
case FETCH_GROUP:
|
||||
case DELETE_GROUP:
|
||||
case UPDATE_GROUP:
|
||||
return Object.assign({}, state, { isWaiting: true});
|
||||
|
||||
case UPDATE_SUCCESS_GROUP:
|
||||
const updatedGroups = updateItemInArray(state.items[action.data.shortName], action.data);
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: {
|
||||
...state.items,
|
||||
[action.data.shortName]: updatedGroups
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
case CREATE_SUCCESS_GROUP:
|
||||
const newGroups = state.items[action.data.shortName].concat(action.data);
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: {
|
||||
...state.items,
|
||||
[action.data.shortName]: newGroups
|
||||
}
|
||||
});
|
||||
|
||||
case DELETE_SUCCESS_GROUP:
|
||||
const delGroups = state.items[action.data.shortName].filter(x => x._id !== action.data.groupId);
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: {
|
||||
...state.items,
|
||||
[action.data.shortName]: delGroups
|
||||
}
|
||||
});
|
||||
|
||||
case FETCH_SUCCESS_GROUP:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: {
|
||||
...state.items,
|
||||
[action.data.shortName]: action.data.groups
|
||||
}
|
||||
});
|
||||
|
||||
case SAVE_ORDER_SUCCESS_GROUP:
|
||||
return Object.assign({}, state, {
|
||||
orderChange: false,
|
||||
isWaiting: false
|
||||
});
|
||||
|
||||
case SAVE_ORDER_ERROR_GROUP:
|
||||
case FETCH_ERROR_GROUP:
|
||||
case CREATE_ERROR_GROUP:
|
||||
case DELETE_ERROR_GROUP:
|
||||
case UPDATE_ERROR_GROUP:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false
|
||||
});
|
||||
|
||||
case REORDER_GROUP:
|
||||
if ((action.data.newIndex<0)||(action.data.newIndex>(state.items[action.data.shortName].length-1))) {
|
||||
return state
|
||||
}
|
||||
const moveGroups = state.items[action.data.shortName].slice();
|
||||
moveGroups.splice(action.data.newIndex, 0, moveGroups.splice(action.data.oldIndex, 1)[0]);
|
||||
return Object.assign({}, state, {
|
||||
orderChange: true,
|
||||
items: {
|
||||
...state.items,
|
||||
[action.data.shortName]: moveGroups
|
||||
}
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default group;
|
||||
@@ -1,148 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import {
|
||||
Container,
|
||||
Checkbox,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Modal,
|
||||
Rail,
|
||||
Segment,
|
||||
List,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Icon,
|
||||
Table
|
||||
} from "semantic-ui-react";
|
||||
|
||||
// ----------------------------------------------------
|
||||
const requestMessageStyle = {
|
||||
color: "red"
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
class AddPermissionModal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
// Set initial state
|
||||
this.state = {
|
||||
role: 5,
|
||||
email: "",
|
||||
error: ""
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
handleChange = (e, { newRole }) => this.setState({ newRole })
|
||||
|
||||
|
||||
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({
|
||||
error: ""
|
||||
});
|
||||
this.props.onClose(false);
|
||||
}
|
||||
handleSubmit() {
|
||||
//if (this.props.updatePermissionId) {
|
||||
const data = {email: this.state.email, shortName: this.props.shortName, role: this.state.role }
|
||||
|
||||
this.props.permissionActions.addPermission(data).then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
error: requestMessage
|
||||
});
|
||||
//this.props.onClose(false);
|
||||
} else {
|
||||
this.setState({
|
||||
error: ""
|
||||
});
|
||||
this.props.onClose(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const railStyle = {
|
||||
top: "15px",
|
||||
minHeight: "200px"
|
||||
};
|
||||
var errorMessage = "";
|
||||
|
||||
if (this.state.error) {
|
||||
errorMessage = (
|
||||
<Message negative>
|
||||
<Message.Header>That didn't work...</Message.Header>
|
||||
<p>{this.state.error}</p>
|
||||
</Message>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal size='tiny' open={this.props.open} onClose={this.handleClose} centered={false}>
|
||||
<Modal.Header>Select Role</Modal.Header>
|
||||
<Modal.Content >
|
||||
<Modal.Description>
|
||||
<Form>
|
||||
<Form.Field>
|
||||
Add User Access to System: {this.props.shortName}
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Input label="User's email" placeholder='user@domain.com' name='email' onChange={this.handleInputChange} />
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
radio
|
||||
label='User'
|
||||
name='role'
|
||||
value={5}
|
||||
checked={(this.state.role >= 0) && (this.state.role < 10)}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
radio
|
||||
label='Admin'
|
||||
name='role'
|
||||
value={15}
|
||||
checked={(this.state.role >= 10) && (this.state.role < 20)}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
{errorMessage}
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={this.handleClose} >
|
||||
<Icon name='remove' /> Cancel
|
||||
</Button>
|
||||
<Button onClick={this.handleSubmit} >
|
||||
<Icon name='plus' /> Add
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddPermissionModal;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as permissionActionCreators from "./permission-actions"
|
||||
import { bindActionCreators } from 'redux'
|
||||
import AddPermissionModal from "./AddPermissionModal"
|
||||
|
||||
//groups: state.group.items[props.match.params.shortName],
|
||||
function mapStateToProps(state,props) {
|
||||
return {
|
||||
permissions: state.permission.items,
|
||||
shortName: props.shortName
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
permissionActions: bindActionCreators(permissionActionCreators,dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AddPermissionModal)
|
||||
@@ -1,76 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Link } from 'react-router-dom'
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Icon,
|
||||
Table
|
||||
} from "semantic-ui-react";
|
||||
|
||||
// ----------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
class ListPermissions extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
|
||||
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
|
||||
render() {
|
||||
const permissions = this.props.permissions;
|
||||
if (permissions) {
|
||||
return (
|
||||
|
||||
<Table >
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Name</Table.HeaderCell>
|
||||
<Table.HeaderCell>Email</Table.HeaderCell>
|
||||
<Table.HeaderCell>Role</Table.HeaderCell>
|
||||
<Table.HeaderCell></Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{permissions.map((permission, i) => (
|
||||
<Table.Row key={ "Group-" + i}>
|
||||
<Table.Cell>{permission.firstName} {permission.lastName}</Table.Cell>
|
||||
<Table.Cell>{permission.email}</Table.Cell>
|
||||
<Table.Cell>{permission.role}</Table.Cell>
|
||||
<Table.Cell><Icon
|
||||
name="pencil alternate"
|
||||
link={true}
|
||||
onClick={e => this.props.updatePermission(permission._id)}
|
||||
/>
|
||||
<Icon
|
||||
name="remove"
|
||||
link={true}
|
||||
onClick={e => this.props.deletePermission(permission)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ListPermissions;
|
||||
@@ -1,134 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import {
|
||||
Container,
|
||||
Checkbox,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Modal,
|
||||
Rail,
|
||||
Segment,
|
||||
List,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Icon,
|
||||
Table
|
||||
} from "semantic-ui-react";
|
||||
|
||||
// ----------------------------------------------------
|
||||
const requestMessageStyle = {
|
||||
color: "red"
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
class UpdatePermissionModal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
// Set initial state
|
||||
this.state = {
|
||||
newRole: false,
|
||||
permission: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
||||
if (nextProps.permissions && nextProps.updatePermissionId) {
|
||||
var updatePermission = nextProps.permissions.find(permission=> permission._id === nextProps.updatePermissionId);
|
||||
|
||||
if (updatePermission) {
|
||||
this.setState({permission: updatePermission, newRole: updatePermission.role});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = (e, { name, value }) => this.setState({ newRole: value })
|
||||
|
||||
|
||||
|
||||
|
||||
handleClose = () => this.props.onClose(false);
|
||||
handleSubmit() {
|
||||
if (this.props.updatePermissionId) {
|
||||
const data = {permissionId: this.props.updatePermissionId, shortName: this.props.shortName, role: this.state.newRole }
|
||||
|
||||
this.props.permissionActions.update(data).then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
requestMessage
|
||||
});
|
||||
this.props.onClose(false);
|
||||
} else {
|
||||
this.props.onClose(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const railStyle = {
|
||||
top: "15px",
|
||||
minHeight: "200px"
|
||||
};
|
||||
var user = ""
|
||||
if (this.state.permission) {
|
||||
user = this.state.permission.email
|
||||
}
|
||||
return (
|
||||
<Modal size='tiny' open={this.props.open} onClose={this.handleClose} centered={false}>
|
||||
<Modal.Header>Select Role</Modal.Header>
|
||||
<Modal.Content >
|
||||
<Modal.Description>
|
||||
<Form>
|
||||
<Form.Field>
|
||||
Set role for <b>{user}</b>:
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
radio
|
||||
label='User'
|
||||
name='checkboxRadioGroup'
|
||||
value={5}
|
||||
checked={(this.state.newRole >= 0) && (this.state.newRole < 10)}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
radio
|
||||
label='Admin'
|
||||
name='checkboxRadioGroup'
|
||||
value={15}
|
||||
checked={(this.state.newRole >= 10) && (this.state.newRole < 20)}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={this.handleSubmit} >
|
||||
<Icon name='checkmark' /> Update
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UpdatePermissionModal;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as permissionActionCreators from "./permission-actions"
|
||||
import { bindActionCreators } from 'redux'
|
||||
import UpdatePermissionModal from "./UpdatePermissionModal"
|
||||
|
||||
//groups: state.group.items[props.match.params.shortName],
|
||||
function mapStateToProps(state,props) {
|
||||
return {
|
||||
permissions: state.permission.items,
|
||||
shortName: props.shortName
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
permissionActions: bindActionCreators(permissionActionCreators,dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(UpdatePermissionModal)
|
||||
@@ -1,166 +0,0 @@
|
||||
import axios from "axios";
|
||||
//import { browserHistory } from "react-router"
|
||||
import * as types from "./permission-constants";
|
||||
|
||||
function beginAddPermission() {
|
||||
return { type: types.ADD_PERMISSION };
|
||||
}
|
||||
|
||||
function addPermissionSuccess(data) {
|
||||
return { type: types.ADD_PERMISSION_SUCCESS, data };
|
||||
}
|
||||
|
||||
function addPermissionError() {
|
||||
return { type: types.ADD_PERMISSION_ERROR };
|
||||
}
|
||||
|
||||
function beginDeletePermission() {
|
||||
return { type: types.DELETE_PERMISSION };
|
||||
}
|
||||
|
||||
function deletePermissionSuccess(data) {
|
||||
return { type: types.DELETE_PERMISSION_SUCCESS, data };
|
||||
}
|
||||
|
||||
function deletePermissionError() {
|
||||
return { type: types.DELETE_PERMISSION_ERROR };
|
||||
}
|
||||
|
||||
|
||||
function beginUpdateRole() {
|
||||
return { type: types.UPDATE_ROLE };
|
||||
}
|
||||
|
||||
function updateRoleSuccess(data) {
|
||||
return { type: types.UPDATE_ROLE_SUCCESS, data };
|
||||
}
|
||||
|
||||
function updateRoleError() {
|
||||
return { type: types.UPDATE_ROLE_ERROR };
|
||||
}
|
||||
|
||||
// "Log In" action creators
|
||||
function beginFetchPermission() {
|
||||
return { type: types.FETCH_PERMISSION };
|
||||
}
|
||||
|
||||
function fetchPermissionError(data) {
|
||||
return { type: types.FETCH_ERROR_PERMISSION,
|
||||
data };
|
||||
}
|
||||
|
||||
function fetchPermissionSuccess(data) {
|
||||
return { type: types.FETCH_SUCCESS_PERMISSION,
|
||||
data };
|
||||
}
|
||||
|
||||
function shouldFetch(state) {
|
||||
if ((!state.permission.items) || (state.permission.items.length <1)) {
|
||||
return true
|
||||
} else if (state.permission.isWaiting) {
|
||||
return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function fetch(shortName) {
|
||||
return (dispatch, getState) => {
|
||||
if (shouldFetch(getState())) {
|
||||
dispatch(beginFetchPermission());
|
||||
|
||||
return axios
|
||||
.get(process.env.REACT_APP_ADMIN_SERVER + "/permissions/"+shortName, {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(fetchPermissionSuccess(response.data.permissions));
|
||||
} else {
|
||||
dispatch(fetchPermissionError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function update(data) {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch(beginUpdateRole());
|
||||
var url = process.env.REACT_APP_ADMIN_SERVER + "/permissions/" + data.shortName + "/" + data.permissionId;
|
||||
return axios.post( url, data, {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(updateRoleSuccess(response.data.permission));
|
||||
} else {
|
||||
dispatch(updateRoleError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export function addPermission(data) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(beginAddPermission());
|
||||
var url = process.env.REACT_APP_ADMIN_SERVER + "/permissions/" + data.shortName;
|
||||
return axios.post( url, data, {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(addPermissionSuccess(response.data.permission));
|
||||
} else {
|
||||
dispatch(addPermissionError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function deletePermission(data) {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch(beginDeletePermission());
|
||||
var url = process.env.REACT_APP_ADMIN_SERVER + "/permissions/" + data.shortName + "/" + data.permissionId;
|
||||
return axios.delete( url, {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(deletePermissionSuccess(data.permissionId));
|
||||
} else {
|
||||
dispatch(deletePermissionError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export const ADD_PERMISSION = "ADD_PERMISSION"
|
||||
export const ADD_PERMISSION_ERROR = "ADD_PERMISSION_ERROR"
|
||||
export const ADD_PERMISSION_SUCCESS = "ADD_PERMISSION_SUCCESS"
|
||||
export const FETCH_PERMISSION = "FETCH_PERMISSION"
|
||||
export const FETCH_ERROR_PERMISSION = "FETCH_ERROR_PERMISSION"
|
||||
export const FETCH_SUCCESS_PERMISSION = "FETCH_SUCCESS_PERMISSION"
|
||||
export const UPDATE_ROLE = "UPDATE_ROLE"
|
||||
export const UPDATE_ROLE_ERROR = "UPDATE_ROLE_ERROR"
|
||||
export const UPDATE_ROLE_SUCCESS = "UPDATE_ROLE SUCCESS"
|
||||
export const DELETE_PERMISSION = "DELETE_PERMISSION"
|
||||
export const DELETE_PERMISSION_ERROR = "DELETE_PERMISSION_ERROR"
|
||||
export const DELETE_PERMISSION_SUCCESS = "DELETE_PERMISSION_SUCCESS"
|
||||
@@ -1,87 +0,0 @@
|
||||
import {
|
||||
ADD_PERMISSION,
|
||||
ADD_PERMISSION_SUCCESS,
|
||||
ADD_PERMISSION_ERROR,
|
||||
DELETE_PERMISSION,
|
||||
DELETE_PERMISSION_SUCCESS,
|
||||
DELETE_PERMISSION_ERROR,
|
||||
FETCH_PERMISSION,
|
||||
FETCH_SUCCESS_PERMISSION,
|
||||
FETCH_ERROR_PERMISSION,
|
||||
UPDATE_ROLE,
|
||||
UPDATE_ROLE_ERROR,
|
||||
UPDATE_ROLE_SUCCESS
|
||||
} from "./permission-constants";
|
||||
|
||||
function updateRoleInArray(array, updatedItem) {
|
||||
const updatedItems = array.map(item => {
|
||||
if(item._id !== updatedItem._id) {
|
||||
// Since we only want to update one item, preserve all others as they are now
|
||||
return item;
|
||||
}
|
||||
item.role = updatedItem.role;
|
||||
return item;
|
||||
});
|
||||
|
||||
return updatedItems;
|
||||
}
|
||||
|
||||
function deleteItemInArray(array, _id) {
|
||||
const updatedItems = array.filter(function (item) {
|
||||
return item._id !== _id;
|
||||
});
|
||||
|
||||
return updatedItems;
|
||||
}
|
||||
|
||||
const permission = (
|
||||
state = {
|
||||
isWaiting: false,
|
||||
items: []
|
||||
},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
||||
|
||||
case ADD_PERMISSION_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: [...state.items, action.data]
|
||||
});
|
||||
case DELETE_PERMISSION_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: deleteItemInArray(state.items, action.data)
|
||||
});
|
||||
case UPDATE_ROLE_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: updateRoleInArray(state.items, action.data)
|
||||
});
|
||||
case FETCH_SUCCESS_PERMISSION:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: action.data
|
||||
});
|
||||
|
||||
case DELETE_PERMISSION_ERROR:
|
||||
case ADD_PERMISSION_ERROR:
|
||||
case UPDATE_ROLE_ERROR:
|
||||
case FETCH_ERROR_PERMISSION:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false
|
||||
});
|
||||
|
||||
case DELETE_PERMISSION:
|
||||
case ADD_PERMISSION:
|
||||
case UPDATE_ROLE:
|
||||
case FETCH_PERMISSION:
|
||||
return Object.assign({}, state, { isWaiting: true});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default permission;
|
||||
@@ -1,103 +1,77 @@
|
||||
import React, { Component } from "react";
|
||||
import React from "react";
|
||||
import { Group } from '@visx/group';
|
||||
import { curveStep} from '@visx/curve';
|
||||
import { LinePath } from '@visx/shape';
|
||||
import { scaleLinear, scaleBand, scaleTime } from '@visx/scale';
|
||||
import { scaleLinear, scaleTime } from '@visx/scale';
|
||||
import { timeParse, timeFormat } from 'd3-time-format';
|
||||
import { AxisBottom, AxisLeft } from '@visx/axis';
|
||||
import { Dimmer, Loader } from "semantic-ui-react";
|
||||
import { Loader } from "semantic-ui-react";
|
||||
|
||||
const CallChart = (props) => {
|
||||
|
||||
class CallChart extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
var callData = props.data;
|
||||
if (!callData) {
|
||||
return (
|
||||
<Loader size='large'>Loading</Loader>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
var callData = this.props.data;
|
||||
if (!callData) {
|
||||
return (
|
||||
var callTotals = callData.callTotals;
|
||||
var errorTotals = callData.errorTotals;
|
||||
|
||||
<Loader size='large'>Loading</Loader>
|
||||
// Define the graph dimensions and margins
|
||||
const width = props.width;
|
||||
const height = 500;
|
||||
const margin = { top: 20, bottom: 40, left: 40, right: 20 };
|
||||
const parseDate = timeParse("%Y%m%d");
|
||||
const format = timeFormat("%b %d");
|
||||
const formatDate = (date) => format(parseDate(date));
|
||||
|
||||
)
|
||||
}
|
||||
var callTotals = [];
|
||||
var errorTotals = [];
|
||||
// Then we'll create some bounds
|
||||
const xMax = width - margin.left - margin.right;
|
||||
const yMax = height - margin.top - margin.bottom;
|
||||
|
||||
// Define the graph dimensions and margins
|
||||
const width = this.props.width;
|
||||
const height = 500;
|
||||
const margin = { top: 20, bottom: 40, left: 40, right: 20 };
|
||||
const parseDate = timeParse("%Y%m%d");
|
||||
const format = timeFormat("%b %d");
|
||||
const formatDate = (date) => format(parseDate(date));
|
||||
// accessors
|
||||
const x = d => d.x;
|
||||
const y = d => d.y;
|
||||
|
||||
// Then we'll create some bounds
|
||||
const xMax = width - margin.left - margin.right;
|
||||
const yMax = height - margin.top - margin.bottom;
|
||||
|
||||
// accessors
|
||||
const x = d => d.x;
|
||||
const y = d => d.y;
|
||||
|
||||
|
||||
|
||||
// Compose together the scale and accessor functions to get point functions
|
||||
const dotColor = n => {
|
||||
var colores_g = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"];
|
||||
return colores_g[n % colores_g.length];
|
||||
}
|
||||
|
||||
const compose = (scale, accessor) => (data) => scale(accessor(data));
|
||||
var xScale, yScale, xPoint, yPoint;
|
||||
if (callData) {
|
||||
// We'll use some mock data from `@vx/mock-data` for this.
|
||||
callTotals = callData.callTotals;
|
||||
errorTotals = callData.errorTotals;
|
||||
// scales
|
||||
xScale = scaleTime({
|
||||
range: [0, xMax],
|
||||
domain: [callData.minDate, callData.maxDate],
|
||||
tickFormat: () => (val) => formatDate(val)
|
||||
});
|
||||
yScale = scaleLinear({
|
||||
range: [yMax, 0],
|
||||
domain: [0, callData.maxValue],
|
||||
});
|
||||
xPoint = compose(xScale, x);
|
||||
yPoint = compose(yScale, y);
|
||||
}
|
||||
var xScale, yScale;
|
||||
if (callData) {
|
||||
// We'll use some mock data from `@vx/mock-data` for this.
|
||||
// scales
|
||||
xScale = scaleTime({
|
||||
range: [0, xMax],
|
||||
domain: [callData.minDate, callData.maxDate],
|
||||
tickFormat: () => (val) => formatDate(val)
|
||||
});
|
||||
yScale = scaleLinear({
|
||||
range: [yMax, 0],
|
||||
domain: [0, callData.maxValue],
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<svg width={width} height={height}>
|
||||
<Group left={margin.left} top={margin.top} >
|
||||
<AxisBottom top={yMax} scale={xScale} numTicks={width > 520 ? 10 : 5} />
|
||||
<AxisLeft scale={yScale} />
|
||||
<LinePath
|
||||
data={callTotals}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
x={x}
|
||||
y={y}
|
||||
strokeWidth={2}
|
||||
curve={curveStep}
|
||||
/>
|
||||
<LinePath
|
||||
data={errorTotals}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
x={x}
|
||||
y={y}
|
||||
strokeWidth={2}
|
||||
stroke="red"
|
||||
curve={curveStep}
|
||||
/>
|
||||
</Group>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
<svg width={width} height={height}>
|
||||
<Group left={margin.left} top={margin.top}>
|
||||
<AxisBottom top={yMax} scale={xScale} numTicks={width > 520 ? 10 : 5} />
|
||||
<AxisLeft scale={yScale} />
|
||||
<LinePath
|
||||
data={callTotals}
|
||||
x={x}
|
||||
y={y}
|
||||
strokeWidth={2}
|
||||
curve={curveStep}
|
||||
/>
|
||||
<LinePath
|
||||
data={errorTotals}
|
||||
x={x}
|
||||
y={y}
|
||||
strokeWidth={2}
|
||||
stroke="red"
|
||||
curve={curveStep}
|
||||
/>
|
||||
</Group>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export default CallChart;
|
||||
|
||||
@@ -1,55 +1,34 @@
|
||||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
Button,
|
||||
Divider,
|
||||
Message,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
|
||||
import { useCreateSystemMutation} from '../features/api/apiSlice'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import SystemForm from "./SystemForm";
|
||||
|
||||
// ----------------------------------------------------
|
||||
const requestMessageStyle = {
|
||||
color: "red"
|
||||
};
|
||||
const CreateSystem = () => {
|
||||
const navigate = useNavigate();
|
||||
const [createSystem ] = useCreateSystemMutation();
|
||||
const { screenName } = useSelector((state) => state.user);
|
||||
|
||||
// ----------------------------------------------------
|
||||
class CreateSystem extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
const [requestMessage, setRequestMessage] = useState("");
|
||||
|
||||
const handleSubmit = async (system) => {
|
||||
try {
|
||||
await createSystem(system).unwrap();
|
||||
navigate("/list-systems")
|
||||
} catch (error) {
|
||||
const message = error.data.message;
|
||||
console.log(message);
|
||||
setRequestMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
state = {
|
||||
requestMessage: ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
handleSubmit(system) {
|
||||
this.props
|
||||
.createSystem(system)
|
||||
.then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
requestMessage
|
||||
});
|
||||
} else {
|
||||
this.props.changeUrl("/list-systems")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Container text>
|
||||
@@ -72,11 +51,11 @@ class CreateSystem extends Component {
|
||||
|
||||
<Container text>
|
||||
<Header as="h1">Create System</Header>
|
||||
<SystemForm onSubmit={this.handleSubmit} requestMessage={this.state.requestMessage} screenName={this.props.user.screenName}/>
|
||||
<SystemForm onSubmit={handleSubmit} requestMessage={requestMessage} screenName={screenName}/>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CreateSystem;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as systemActions from "./system-actions"
|
||||
import CreateSystem from "./CreateSystem"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
systemActions
|
||||
)(CreateSystem)
|
||||
@@ -7,9 +7,7 @@ import { AxisBottom, AxisLeft } from '@visx/axis';
|
||||
import { Loader } from "semantic-ui-react";
|
||||
import { LegendOrdinal} from '@visx/legend';
|
||||
class ErrorChart extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
var errorData = this.props.data;
|
||||
@@ -44,12 +42,11 @@ class ErrorChart extends Component {
|
||||
return colores_g[n % colores_g.length];
|
||||
}
|
||||
|
||||
const compose = (scale, accessor) => (data) => scale(accessor(data));
|
||||
var zScale = scaleOrdinal({
|
||||
domain: errorData.legend,
|
||||
range: ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"]
|
||||
});
|
||||
var xScale, yScale, xPoint, yPoint;
|
||||
var xScale, yScale;
|
||||
if (errorData) {
|
||||
// We'll use some mock data from `@vx/mock-data` for this.
|
||||
data = errorData.data;
|
||||
@@ -67,8 +64,6 @@ class ErrorChart extends Component {
|
||||
domain: errorData.legend,
|
||||
range: ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"]
|
||||
});
|
||||
xPoint = compose(xScale, x);
|
||||
yPoint = compose(yScale, y);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React, { Component } from "react";
|
||||
import SystemCard from "./SystemCard";
|
||||
import {
|
||||
Container,
|
||||
@@ -8,37 +7,38 @@ import {
|
||||
Icon,
|
||||
Button,
|
||||
} from "semantic-ui-react";
|
||||
import { useGetSystemsQuery, } from '../features/api/apiSlice'
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
|
||||
// ----------------------------------------------------
|
||||
class ListSystems extends Component {
|
||||
|
||||
state = {
|
||||
|
||||
const ListSystems = () => {
|
||||
const { data, isSuccess } = useGetSystemsQuery();
|
||||
const navigate = useNavigate();
|
||||
let systems = false;
|
||||
if (isSuccess) {
|
||||
systems = data.systems;
|
||||
}
|
||||
componentDidMount() {
|
||||
this.props.fetchSystems();
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
|
||||
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
|
||||
render() {
|
||||
const systems = this.props.system.items;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<Header as="h1">Radio Systems</Header>
|
||||
<Button onClick={(e) => this.props.changeUrl("/create-system")}><Icon name='plus' />Add System</Button>
|
||||
<Button onClick={(e) => navigate("/create-system")}><Icon name='plus' />Add System</Button>
|
||||
<Divider />
|
||||
<Card.Group itemsPerRow={4}>
|
||||
{systems.map((system) =>
|
||||
<SystemCard system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/>
|
||||
{systems &&
|
||||
systems.map((system) =>
|
||||
<SystemCard system={system} key={system.shortName} onClick={(e) => navigate("/system/" + system.shortName)}/>
|
||||
)}
|
||||
</Card.Group>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ListSystems;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as systemActions from "./system-actions"
|
||||
import ListSystems from "./ListSystems"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
system: state.system
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
systemActions
|
||||
)(ListSystems)
|
||||
@@ -1,72 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Grid,
|
||||
Segment,
|
||||
Input,
|
||||
Button,
|
||||
Message,
|
||||
Step,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
|
||||
import SystemForm from "./SystemForm";
|
||||
|
||||
// ----------------------------------------------------
|
||||
const requestMessageStyle = {
|
||||
color: "red"
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
class PreCreateSystem extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
|
||||
state = {
|
||||
requestMessage: ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Step.Group attached='top' widths={3}>
|
||||
<Step>
|
||||
<Icon name='wifi' />
|
||||
<Step.Content>
|
||||
<Step.Title>Trunk Recorder</Step.Title>
|
||||
<Step.Description>Choose your shipping options</Step.Description>
|
||||
</Step.Content>
|
||||
</Step>
|
||||
|
||||
<Step active>
|
||||
<Icon name='payment' />
|
||||
<Step.Content>
|
||||
<Step.Title>Billing</Step.Title>
|
||||
<Step.Description>Enter billing information</Step.Description>
|
||||
</Step.Content>
|
||||
</Step>
|
||||
|
||||
<Step disabled>
|
||||
<Icon name='info' />
|
||||
<Step.Content>
|
||||
<Step.Title>Confirm Order</Step.Title>
|
||||
</Step.Content>
|
||||
</Step>
|
||||
</Step.Group>
|
||||
|
||||
<Segment attached>
|
||||
<Image src='https://react.semantic-ui.com/images/wireframe/paragraph.png' />
|
||||
</Segment>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PreCreateSystem;
|
||||
@@ -1,27 +1,22 @@
|
||||
import React, { Component } from "react"
|
||||
import CallChart from "./CallChart"
|
||||
import { ParentSize } from "@visx/responsive";
|
||||
|
||||
class ResponsiveCallChart extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
const ResponsiveCallChart = (props) => {
|
||||
|
||||
render() {
|
||||
return(
|
||||
<ParentSize className="graph-container">
|
||||
{({ width: w, height: h }) => {
|
||||
return (
|
||||
<CallChart
|
||||
width={w}
|
||||
height={h}
|
||||
data={this.props.data}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ParentSize>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ParentSize className="graph-container">
|
||||
{({ width: w, height: h }) => {
|
||||
return (
|
||||
<CallChart
|
||||
width={w}
|
||||
height={h}
|
||||
data={props.data}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ParentSize>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default ResponsiveCallChart
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
import React, { Component } from "react"
|
||||
import ErrorChart from "./ErrorChart"
|
||||
import { ParentSize } from "@visx/responsive";
|
||||
|
||||
class ResponsiveErrorChart extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
const ResponsiveErrorChart = (props) => {
|
||||
|
||||
render() {
|
||||
return(
|
||||
<ParentSize className="graph-container">
|
||||
{({ width: w, height: h }) => {
|
||||
return (
|
||||
<ErrorChart
|
||||
width={w}
|
||||
height={h}
|
||||
data={this.props.data}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ParentSize>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ParentSize className="graph-container">
|
||||
{({ width: w, height: h }) => {
|
||||
return (
|
||||
<ErrorChart
|
||||
width={w}
|
||||
height={h}
|
||||
data={props.data}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ParentSize>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResponsiveErrorChart
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
|
||||
import React, { Component } from "react";
|
||||
import {
|
||||
Card,
|
||||
Icon,
|
||||
@@ -9,15 +8,13 @@ import {
|
||||
|
||||
|
||||
|
||||
class SystemCard extends Component {
|
||||
|
||||
render() {
|
||||
const SystemCard = (props) => {
|
||||
var location = "";
|
||||
const system = this.props.system;
|
||||
const { system } = props;
|
||||
|
||||
|
||||
|
||||
if (system) {
|
||||
|
||||
|
||||
|
||||
switch (system.systemType) {
|
||||
case "state":
|
||||
@@ -37,26 +34,26 @@ render() {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Card link onClick={this.props.onClick}>
|
||||
|
||||
<Card.Content>
|
||||
<Header size='medium'>
|
||||
<Header.Content>
|
||||
{system.name}
|
||||
<Header.Subheader>{system.screenName}</Header.Subheader>
|
||||
</Header.Content>
|
||||
</Header>
|
||||
</Card.Content><Card.Content>
|
||||
<Card link onClick={props.onClick}>
|
||||
|
||||
<Card.Description>{system.description}</Card.Description>
|
||||
</Card.Content>
|
||||
<Card.Content extra>
|
||||
<Icon name='marker' />
|
||||
{location}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
<Card.Content>
|
||||
<Header size='medium'>
|
||||
<Header.Content>
|
||||
{system.name}
|
||||
<Header.Subheader>{system.screenName}</Header.Subheader>
|
||||
</Header.Content>
|
||||
</Header>
|
||||
</Card.Content><Card.Content>
|
||||
|
||||
<Card.Description>{system.description}</Card.Description>
|
||||
</Card.Content>
|
||||
<Card.Content extra>
|
||||
<Icon name='marker' />
|
||||
{location}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SystemCard;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import * as systemActionCreators from "./system-actions"
|
||||
import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions"
|
||||
import * as groupActionCreators from "../Group/group-actions"
|
||||
import * as permissionActionCreators from "../Permission/permission-actions"
|
||||
import * as errorActionCreators from "./error-actions"
|
||||
import { bindActionCreators } from 'redux'
|
||||
import System from "./System"
|
||||
|
||||
function mapStateToProps(state,props) {
|
||||
return {
|
||||
system: state.system.items.find((item) => item.shortName === props.match.params.shortName),
|
||||
permissions: state.permission.items,
|
||||
statistic: state.statistic[props.match.params.shortName],
|
||||
systemIsWaiting: state.system.isWaiting,
|
||||
talkgroupsIsWaiting: state.talkgroup.isWaiting,
|
||||
groupsIsWaiting: state.group.isWaiting,
|
||||
talkgroups: state.talkgroup.items[props.match.params.shortName],
|
||||
orderChange: state.group.orderChange,
|
||||
groups: state.group.items[props.match.params.shortName],
|
||||
errors: state.error.items[props.match.params.shortName],
|
||||
shortName: props.match.params.shortName,
|
||||
activeTab: (props.match.params.tab ? props.match.params.tab : 0)
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
groupActions: bindActionCreators(groupActionCreators, dispatch),
|
||||
systemActions: bindActionCreators(systemActionCreators, dispatch),
|
||||
talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch),
|
||||
errorActions: bindActionCreators(errorActionCreators, dispatch),
|
||||
permissionActions: bindActionCreators(permissionActionCreators, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(System)
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Divider,
|
||||
Form,
|
||||
@@ -9,421 +9,385 @@ import {
|
||||
} from "semantic-ui-react";
|
||||
|
||||
// ----------------------------------------------------
|
||||
class SystemForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleScreenNameCheckboxChange = this.handleScreenNameCheckboxChange.bind(this);
|
||||
this.handleTalkgroupCheckboxChange = this.handleTalkgroupCheckboxChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.checkInputs = this.checkInputs.bind(this);
|
||||
this.render = this.render.bind(this);
|
||||
const SystemForm = (props) => {
|
||||
|
||||
|
||||
this.state = {
|
||||
checkInputMessages: [],
|
||||
name: "",
|
||||
shortName: "",
|
||||
description: "",
|
||||
systemType: "state",
|
||||
state: "",
|
||||
city: "",
|
||||
county: "",
|
||||
country: "",
|
||||
showScreenName: false,
|
||||
ignoreUnknownTalkgroup: false,
|
||||
nameError: false,
|
||||
shortNameError: false,
|
||||
descriptionError: false,
|
||||
emailError: false,
|
||||
systemTypeError: false,
|
||||
stateError: false,
|
||||
cityErorr: false,
|
||||
countyError: false,
|
||||
countryError: false,
|
||||
isEditing: this.props.isEditing
|
||||
};
|
||||
if (this.props.isEditing && this.props.system) {
|
||||
this.state.name = this.props.system.name;
|
||||
this.state.shortName = this.props.system.shortName;
|
||||
this.state.description = this.props.system.description;
|
||||
this.state.systemType = this.props.system.systemType;
|
||||
this.state.city = this.props.system.city;
|
||||
this.state.state = this.props.system.state;
|
||||
this.state.county = this.props.system.county;
|
||||
this.state.country = this.props.system.country;
|
||||
this.state.showScreenName = this.props.system.showScreenName;
|
||||
this.state.ignoreUnknownTalkgroup = this.props.system.ignoreUnknownTalkgroup;
|
||||
|
||||
|
||||
const [checkInputMessages, setCheckInputMessage] = useState([]);
|
||||
const [name, setName] = useState("");
|
||||
const [shortName, setShortName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [systemType, setSystemType] = useState("state");
|
||||
const [systemState, setSystemState] = useState("");
|
||||
const [city, setCity] = useState("");
|
||||
const [county, setCounty] = useState("");
|
||||
const [country, setCountry] = useState("");
|
||||
const [showScreenName, setShowScreenName] = useState(false);
|
||||
const [ignoreUnknownTalkgroup, setIgnoreUnknownTalkgroup] = useState(false);
|
||||
const [nameError, setNameError] = useState(false);
|
||||
const [shortNameError, setShortNameError] = useState(false);
|
||||
const [descriptionError, setDescriptionError] = useState(false);
|
||||
const [systemStateError, setSystemStateError] = useState(false);
|
||||
const [cityError, setCityError] = useState(false);
|
||||
const [countyError, setCountyError] = useState(false);
|
||||
const [countryError, setCountryError] = useState(false);
|
||||
const [isEditing] = useState(props.isEditing);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isEditing && props.system) {
|
||||
setName(props.system.name);
|
||||
setShortName(props.system.shortName);
|
||||
setDescription(props.system.description);
|
||||
setSystemType(props.system.systemType);
|
||||
setCity(props.system.city);
|
||||
setSystemState(props.system.state)
|
||||
setCounty(props.system.county);
|
||||
setCountry(props.system.country);
|
||||
setShowScreenName(props.system.showScreenName);
|
||||
setIgnoreUnknownTalkgroup(props.system.ignoreUnknownTalkgroup);
|
||||
}
|
||||
}
|
||||
// If we directly to an updating url, the system fetch will come back after the constructer, so we need to catch this and update the values
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.isEditing && this.props.system && !prevProps.system) {
|
||||
this.setState({
|
||||
name: this.props.system.name,
|
||||
shortName: this.props.system.shortName,
|
||||
description: this.props.system.description,
|
||||
systemType: this.props.system.systemType,
|
||||
city: this.props.system.city,
|
||||
state: this.props.system.state,
|
||||
county: this.props.system.county,
|
||||
country: this.props.system.country,
|
||||
showScreenName: this.props.system.showScreenName,
|
||||
ignoreUnknownTalkgroup: this.props.system.ignoreUnknownTalkgroup,
|
||||
});
|
||||
}
|
||||
}
|
||||
checkInputs() {
|
||||
}, [props.system, props.isEditing]);
|
||||
|
||||
|
||||
const checkInputs = () => {
|
||||
let error = false;
|
||||
var checkInputMessages = [];
|
||||
var messages = [];
|
||||
|
||||
if (this.state.name === "") {
|
||||
this.setState({ nameError: true });
|
||||
checkInputMessages.push("System Name is required");
|
||||
if (name === "") {
|
||||
setNameError(true);
|
||||
messages.push("System Name is required");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ nameError: false });
|
||||
setNameError(false);
|
||||
}
|
||||
|
||||
if (this.state.shortName.length === 0 || this.state.shortName.length > 10) {
|
||||
this.setState({ shortNameError: true });
|
||||
checkInputMessages.push(
|
||||
if (shortName.length < 4 || shortName.length > 10) {
|
||||
setShortNameError(true)
|
||||
messages.push(
|
||||
"Short Name for the system has to be 4-10 charecters"
|
||||
);
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ shortNameError: false });
|
||||
setShortNameError(false);
|
||||
}
|
||||
|
||||
if (this.state.description === "") {
|
||||
this.setState({ descriptionError: true });
|
||||
checkInputMessages.push("System description is required");
|
||||
if (description === "") {
|
||||
setDescriptionError(true);
|
||||
messages.push("System description is required");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ descriptionError: false });
|
||||
setDescriptionError(false);
|
||||
}
|
||||
|
||||
if (
|
||||
this.state.systemType === "state" ||
|
||||
this.state.systemType === "city" ||
|
||||
this.state.systemType === "county"
|
||||
systemType === "state" ||
|
||||
systemType === "city" ||
|
||||
systemType === "county"
|
||||
) {
|
||||
if (this.state.state === "") {
|
||||
this.setState({ stateError: true });
|
||||
checkInputMessages.push("Select a state");
|
||||
if (systemState === "") {
|
||||
setSystemStateError(true);
|
||||
messages.push("Select a state");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ stateError: false });
|
||||
setSystemStateError(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.systemType === "city") {
|
||||
if (this.state.city === "") {
|
||||
this.setState({ cityError: true });
|
||||
checkInputMessages.push("Enter a city");
|
||||
if (systemType === "city") {
|
||||
if (city === "") {
|
||||
setCityError(true);
|
||||
messages.push("Enter a city");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ cityError: false });
|
||||
setCityError(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.systemType === "county") {
|
||||
if (this.state.county === "") {
|
||||
this.setState({ countyError: true });
|
||||
checkInputMessages.push("Enter a city");
|
||||
if (systemType === "county") {
|
||||
if (county === "") {
|
||||
setCountyError(true)
|
||||
messages.push("Enter a county");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ countyError: false });
|
||||
setCountyError(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.systemType === "international") {
|
||||
if (this.state.country === "") {
|
||||
this.setState({ countryError: true });
|
||||
checkInputMessages.push("Enter a city");
|
||||
if (systemType === "international") {
|
||||
if (country === "") {
|
||||
setCountryError(true);
|
||||
messages.push("Enter a country");
|
||||
error = true;
|
||||
} else {
|
||||
this.setState({ countryError: false });
|
||||
setCountryError(false);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ checkInputMessages: checkInputMessages });
|
||||
setCheckInputMessage(messages);
|
||||
return error;
|
||||
}
|
||||
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
handleScreenNameCheckboxChange() {
|
||||
this.setState({ showScreenName: !this.state.showScreenName });
|
||||
const handleScreenNameCheckboxChange = () => {
|
||||
setShowScreenName((current) => !current);
|
||||
}
|
||||
|
||||
handleTalkgroupCheckboxChange() {
|
||||
this.setState({ ignoreUnknownTalkgroup: !this.state.ignoreUnknownTalkgroup });
|
||||
const handleTalkgroupCheckboxChange = () => {
|
||||
setIgnoreUnknownTalkgroup((current) => !current);
|
||||
}
|
||||
handleSubmit(event) {
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
let inputError = this.checkInputs();
|
||||
let inputError = checkInputs();
|
||||
|
||||
if (!inputError) {
|
||||
const system = (({
|
||||
const system = {
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
state: systemState,
|
||||
county,
|
||||
country,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}) => ({
|
||||
name,
|
||||
shortName,
|
||||
description,
|
||||
systemType,
|
||||
city,
|
||||
state,
|
||||
county,
|
||||
country,
|
||||
showScreenName,
|
||||
ignoreUnknownTalkgroup
|
||||
}))(this.state);
|
||||
this.props.onSubmit(system);
|
||||
};
|
||||
props.onSubmit(system);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
var requestMessage = "";
|
||||
var floatStyle = {
|
||||
overflow: "auto"
|
||||
};
|
||||
|
||||
const { isEditing } = this.props;
|
||||
const stateOptions = [
|
||||
{ value: "AL", text: "Alabama" },
|
||||
{ value: "AK", text: "Alaska" },
|
||||
{ value: "AZ", text: "Arizona" },
|
||||
{ value: "AR", text: "Arkansas" },
|
||||
{ value: "CA", text: "California" },
|
||||
{ value: "CO", text: "Colorado" },
|
||||
{ value: "CT", text: "Connecticut" },
|
||||
{ value: "DE", text: "Delaware" },
|
||||
{ value: "DC", text: "District Of Columbia" },
|
||||
{ value: "FL", text: "Florida" },
|
||||
{ value: "GA", text: "Georgia" },
|
||||
{ value: "HI", text: "Hawaii" },
|
||||
{ value: "ID", text: "Idaho" },
|
||||
{ value: "IL", text: "Illinois" },
|
||||
{ value: "IN", text: "Indiana" },
|
||||
{ value: "IA", text: "Iowa" },
|
||||
{ value: "KS", text: "Kansas" },
|
||||
{ value: "KY", text: "Kentucky" },
|
||||
{ value: "LA", text: "Louisiana" },
|
||||
{ value: "ME", text: "Maine" },
|
||||
{ value: "MD", text: "Maryland" },
|
||||
{ value: "MA", text: "Massachusetts" },
|
||||
{ value: "MI", text: "Michigan" },
|
||||
{ value: "MN", text: "Minnesota" },
|
||||
{ value: "MS", text: "Mississippi" },
|
||||
{ value: "MO", text: "Missouri" },
|
||||
{ value: "MT", text: "Montana" },
|
||||
{ value: "NE", text: "Nebraska" },
|
||||
{ value: "NV", text: "Nevada" },
|
||||
{ value: "NH", text: "New Hampshire" },
|
||||
{ value: "NJ", text: "New Jersey" },
|
||||
{ value: "NM", text: "New Mexico" },
|
||||
{ value: "NY", text: "New York" },
|
||||
{ value: "NC", text: "North Carolina" },
|
||||
{ value: "ND", text: "North Dakota" },
|
||||
{ value: "OH", text: "Ohio" },
|
||||
{ value: "OK", text: "Oklahoma" },
|
||||
{ value: "OR", text: "Oregon" },
|
||||
{ value: "PA", text: "Pennsylvania" },
|
||||
{ value: "RI", text: "Rhode Island" },
|
||||
{ value: "SC", text: "South Carolina" },
|
||||
{ value: "SD", text: "South Dakota" },
|
||||
{ value: "TN", text: "Tennessee" },
|
||||
{ value: "TX", text: "Texas" },
|
||||
{ value: "UT", text: "Utah" },
|
||||
{ value: "VT", text: "Vermont" },
|
||||
{ value: "VA", text: "Virginia" },
|
||||
{ value: "WA", text: "Washington" },
|
||||
{ value: "WV", text: "West Virginia" },
|
||||
{ value: "WI", text: "Wisconsin" },
|
||||
{ value: "WY", text: "Wyoming" }
|
||||
];
|
||||
if (
|
||||
this.state.checkInputMessages.length ||
|
||||
this.props.requestMessage.length
|
||||
) {
|
||||
var counter = 0;
|
||||
requestMessage = (
|
||||
<Message compact warning icon>
|
||||
<Icon name="lightning" />
|
||||
<Message.Content>
|
||||
<Message.Header>Problems...</Message.Header>
|
||||
<ul>
|
||||
{this.state.checkInputMessages.map(function (listValue) {
|
||||
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
|
||||
})}
|
||||
{this.props.requestMessage.length > 0 && (
|
||||
<li key="req-mess">{this.props.requestMessage}</li>
|
||||
)}
|
||||
</ul>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Form className="raised padding segment" onSubmit={this.handleSubmit}>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="name"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.nameError}
|
||||
value={this.state.name}
|
||||
label="System name"
|
||||
placeholder="System Name..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="shortName"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.shortNameError}
|
||||
value={this.state.shortName}
|
||||
disabled={isEditing}
|
||||
label="System Short Name (4-10 characters)"
|
||||
placeholder="Short Name..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
<Divider horizontal />
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
onChange={this.handleScreenNameCheckboxChange}
|
||||
checked={this.state.showScreenName}
|
||||
label="Display Screen Name in System Listing" />
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
onChange={this.handleTalkgroupCheckboxChange}
|
||||
checked={this.state.ignoreUnknownTalkgroup}
|
||||
label="Ignore Unknown Talkgroups" />
|
||||
</Form.Field>
|
||||
<Divider horizontal />
|
||||
<Form.TextArea
|
||||
label="Description"
|
||||
name="description"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.descriptionError}
|
||||
value={this.state.description}
|
||||
rows={2}
|
||||
placeholder="Description of system..."
|
||||
/>
|
||||
var requestMessage = "";
|
||||
var floatStyle = {
|
||||
overflow: "auto"
|
||||
};
|
||||
|
||||
<Divider horizontal><Icon name="marker" />Location</Divider>
|
||||
<Form.Group inline>
|
||||
<label>System Type</label>
|
||||
<Form.Radio
|
||||
label="State"
|
||||
name="systemType"
|
||||
value="state"
|
||||
checked={this.state.systemType === "state"}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
<Form.Radio
|
||||
label="County"
|
||||
name="systemType"
|
||||
value="county"
|
||||
checked={this.state.systemType === "county"}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
<Form.Radio
|
||||
label="City"
|
||||
name="systemType"
|
||||
value="city"
|
||||
checked={this.state.systemType === "city"}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
<Form.Radio
|
||||
label="International"
|
||||
name="systemType"
|
||||
value="international"
|
||||
checked={this.state.systemType === "international"}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
|
||||
{this.state.systemType === "city" && (
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="city"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.cityError}
|
||||
value={this.state.city}
|
||||
label="City"
|
||||
placeholder="City..."
|
||||
/>
|
||||
</Form.Field>
|
||||
const stateOptions = [
|
||||
{ value: "AL", text: "Alabama" },
|
||||
{ value: "AK", text: "Alaska" },
|
||||
{ value: "AZ", text: "Arizona" },
|
||||
{ value: "AR", text: "Arkansas" },
|
||||
{ value: "CA", text: "California" },
|
||||
{ value: "CO", text: "Colorado" },
|
||||
{ value: "CT", text: "Connecticut" },
|
||||
{ value: "DE", text: "Delaware" },
|
||||
{ value: "DC", text: "District Of Columbia" },
|
||||
{ value: "FL", text: "Florida" },
|
||||
{ value: "GA", text: "Georgia" },
|
||||
{ value: "HI", text: "Hawaii" },
|
||||
{ value: "ID", text: "Idaho" },
|
||||
{ value: "IL", text: "Illinois" },
|
||||
{ value: "IN", text: "Indiana" },
|
||||
{ value: "IA", text: "Iowa" },
|
||||
{ value: "KS", text: "Kansas" },
|
||||
{ value: "KY", text: "Kentucky" },
|
||||
{ value: "LA", text: "Louisiana" },
|
||||
{ value: "ME", text: "Maine" },
|
||||
{ value: "MD", text: "Maryland" },
|
||||
{ value: "MA", text: "Massachusetts" },
|
||||
{ value: "MI", text: "Michigan" },
|
||||
{ value: "MN", text: "Minnesota" },
|
||||
{ value: "MS", text: "Mississippi" },
|
||||
{ value: "MO", text: "Missouri" },
|
||||
{ value: "MT", text: "Montana" },
|
||||
{ value: "NE", text: "Nebraska" },
|
||||
{ value: "NV", text: "Nevada" },
|
||||
{ value: "NH", text: "New Hampshire" },
|
||||
{ value: "NJ", text: "New Jersey" },
|
||||
{ value: "NM", text: "New Mexico" },
|
||||
{ value: "NY", text: "New York" },
|
||||
{ value: "NC", text: "North Carolina" },
|
||||
{ value: "ND", text: "North Dakota" },
|
||||
{ value: "OH", text: "Ohio" },
|
||||
{ value: "OK", text: "Oklahoma" },
|
||||
{ value: "OR", text: "Oregon" },
|
||||
{ value: "PA", text: "Pennsylvania" },
|
||||
{ value: "RI", text: "Rhode Island" },
|
||||
{ value: "SC", text: "South Carolina" },
|
||||
{ value: "SD", text: "South Dakota" },
|
||||
{ value: "TN", text: "Tennessee" },
|
||||
{ value: "TX", text: "Texas" },
|
||||
{ value: "UT", text: "Utah" },
|
||||
{ value: "VT", text: "Vermont" },
|
||||
{ value: "VA", text: "Virginia" },
|
||||
{ value: "WA", text: "Washington" },
|
||||
{ value: "WV", text: "West Virginia" },
|
||||
{ value: "WI", text: "Wisconsin" },
|
||||
{ value: "WY", text: "Wyoming" }
|
||||
];
|
||||
if (
|
||||
checkInputMessages.length ||
|
||||
props.requestMessage.length
|
||||
) {
|
||||
var counter = 0;
|
||||
requestMessage = (
|
||||
<Message compact warning icon>
|
||||
<Icon name="lightning" />
|
||||
<Message.Content>
|
||||
<Message.Header>Problems...</Message.Header>
|
||||
<ul>
|
||||
{checkInputMessages.map(function (listValue) {
|
||||
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
|
||||
})}
|
||||
{props.requestMessage.length > 0 && (
|
||||
<li key="req-mess">{props.requestMessage}</li>
|
||||
)}
|
||||
{this.state.systemType === "county" && (
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="county"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.countyError}
|
||||
value={this.state.county}
|
||||
label="County"
|
||||
placeholder="County..."
|
||||
/>
|
||||
</Form.Field>
|
||||
)}
|
||||
{this.state.systemType === "international" ? (
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="country"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.countryError}
|
||||
value={this.state.country}
|
||||
label="Country"
|
||||
placeholder="Country..."
|
||||
/>
|
||||
</Form.Field>
|
||||
) : (
|
||||
<Form.Field>
|
||||
<Form.Dropdown
|
||||
name="state"
|
||||
onChange={this.handleInputChange}
|
||||
error={this.state.stateError}
|
||||
value={this.state.state}
|
||||
options={stateOptions}
|
||||
scrolling
|
||||
label="State"
|
||||
placeholder="Select State..."
|
||||
/>
|
||||
</Form.Field>
|
||||
)}
|
||||
|
||||
</Form.Group>
|
||||
|
||||
<div style={floatStyle}>
|
||||
<Button type="submit" size="large" floated="right" value="Login">
|
||||
{isEditing !== true ? "Create" : "Update"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
{requestMessage}
|
||||
</div>
|
||||
</ul>
|
||||
</Message.Content>
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Form className="raised padding segment" onSubmit={handleSubmit}>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="name"
|
||||
onChange={e => setName(e.target.value)}
|
||||
error={nameError}
|
||||
value={name}
|
||||
label="System name"
|
||||
placeholder="System Name..."
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="shortName"
|
||||
onChange={e => setShortName(e.target.value)}
|
||||
error={shortNameError}
|
||||
value={shortName}
|
||||
disabled={isEditing}
|
||||
label="System Short Name (4-10 characters)"
|
||||
placeholder="Short Name..."
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
<Divider horizontal />
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
onChange={handleScreenNameCheckboxChange}
|
||||
checked={showScreenName}
|
||||
label="Display Screen Name in System Listing" />
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
onChange={handleTalkgroupCheckboxChange}
|
||||
checked={ignoreUnknownTalkgroup}
|
||||
label="Ignore Unknown Talkgroups" />
|
||||
</Form.Field>
|
||||
<Divider horizontal />
|
||||
<Form.TextArea
|
||||
label="Description"
|
||||
name="description"
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
error={descriptionError}
|
||||
value={description}
|
||||
rows={2}
|
||||
placeholder="Description of system..."
|
||||
/>
|
||||
|
||||
<Divider horizontal><Icon name="marker" />Location</Divider>
|
||||
<Form.Group inline>
|
||||
<label>System Type</label>
|
||||
<Form.Radio
|
||||
label="State"
|
||||
name="systemType"
|
||||
value="state"
|
||||
checked={systemType === "state"}
|
||||
onChange={e => setSystemType("state")}
|
||||
/>
|
||||
<Form.Radio
|
||||
label="County"
|
||||
name="systemType"
|
||||
value="county"
|
||||
checked={systemType === "county"}
|
||||
onChange={e => setSystemType("county")}
|
||||
/>
|
||||
<Form.Radio
|
||||
label="City"
|
||||
name="systemType"
|
||||
value="city"
|
||||
checked={systemType === "city"}
|
||||
onChange={e => setSystemType("city")}
|
||||
/>
|
||||
<Form.Radio
|
||||
label="International"
|
||||
name="systemType"
|
||||
value="international"
|
||||
checked={systemType === "international"}
|
||||
onChange={e => setSystemType("international")}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
|
||||
{systemType === "city" && (
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="city"
|
||||
onChange={e => setCity(e.target.value)}
|
||||
error={cityError}
|
||||
value={city}
|
||||
label="City"
|
||||
placeholder="City..."
|
||||
/>
|
||||
</Form.Field>
|
||||
)}
|
||||
{systemType === "county" && (
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="county"
|
||||
onChange={e => setCounty(e.target.value)}
|
||||
error={countyError}
|
||||
value={county}
|
||||
label="County"
|
||||
placeholder="County..."
|
||||
/>
|
||||
</Form.Field>
|
||||
)}
|
||||
{systemType === "international" ? (
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
type="text"
|
||||
name="country"
|
||||
onChange={e => setCountry(e.target.value)}
|
||||
error={countryError}
|
||||
value={country}
|
||||
label="Country"
|
||||
placeholder="Country..."
|
||||
/>
|
||||
</Form.Field>
|
||||
) : (
|
||||
<Form.Field>
|
||||
<Form.Dropdown
|
||||
name="state"
|
||||
onChange={(e, data ) => {setSystemState(data.value); console.log(data.value)}}
|
||||
error={systemStateError}
|
||||
value={systemState}
|
||||
options={stateOptions}
|
||||
scrolling
|
||||
label="State"
|
||||
placeholder="Select State..."
|
||||
/>
|
||||
</Form.Field>
|
||||
)}
|
||||
|
||||
</Form.Group>
|
||||
|
||||
<div style={floatStyle}>
|
||||
<Button type="submit" size="large" floated="right" value="Login">
|
||||
{isEditing !== true ? "Create" : "Update"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
{requestMessage}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default SystemForm;
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import React, { Component } from "react"
|
||||
import React, { useState } from "react";
|
||||
import SystemForm from "./SystemForm";
|
||||
import {
|
||||
Container,
|
||||
Header
|
||||
} from "semantic-ui-react";
|
||||
import { useUpdateSystemMutation, useGetSystemsQuery, } from '../features/api/apiSlice'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
class UpdateSystem extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSystemSubmit = this.handleSystemSubmit.bind(this);
|
||||
const UpdateSystem = (props) => {
|
||||
|
||||
const { screenName } = useSelector((state) => state.user);
|
||||
const { shortName } = useParams();
|
||||
const { data: systemsData, isSuccess: isSystemsSuccess } = useGetSystemsQuery();
|
||||
const [updateSystem] = useUpdateSystemMutation();
|
||||
const [requestMessage, setRequestMessage] = useState("");
|
||||
|
||||
const navigate = useNavigate();
|
||||
let system = false;
|
||||
if (isSystemsSuccess) {
|
||||
system = systemsData.systems.find(sys => sys.shortName === shortName);
|
||||
}
|
||||
|
||||
state = {
|
||||
requestMessage: ""
|
||||
|
||||
const handleSystemSubmit = async (system) => {
|
||||
try {
|
||||
await updateSystem(system).unwrap();
|
||||
navigate("/system/" + shortName)
|
||||
} catch (error) {
|
||||
const message = error.data.message;
|
||||
console.log(message);
|
||||
setRequestMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchSystems()
|
||||
}
|
||||
|
||||
handleSystemSubmit(system) {
|
||||
this.props
|
||||
.updateSystem(system)
|
||||
.then(requestMessage => {
|
||||
if (requestMessage) {
|
||||
// report to the user is there was a problem during registration
|
||||
this.setState({
|
||||
requestMessage
|
||||
});
|
||||
} else {
|
||||
this.props.changeUrl("/system/"+system.shortName)
|
||||
}
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Container text>
|
||||
<Header as="h1">Update System</Header>
|
||||
<SystemForm onSubmit={handleSystemSubmit} system={system} isEditing={true} requestMessage={requestMessage} screenName={screenName} />
|
||||
</Container>
|
||||
)
|
||||
|
||||
render() {
|
||||
return(
|
||||
<Container text>
|
||||
<Header as="h1">Update System</Header>
|
||||
<SystemForm onSubmit={this.handleSystemSubmit} system={this.props.system} isEditing={true} requestMessage={this.state.requestMessage} screenName={this.props.user.screenName}/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default UpdateSystem
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { connect } from "react-redux"
|
||||
import UpdateSystem from "./UpdateSystem"
|
||||
import * as systemActions from "./system-actions"
|
||||
|
||||
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
|
||||
return {
|
||||
system: state.system.items.find((item) => item.shortName === props.match.params.shortName),
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
systemActions
|
||||
)(UpdateSystem)
|
||||
@@ -1,54 +0,0 @@
|
||||
import axios from "axios";
|
||||
//import { browserHistory } from "react-router"
|
||||
import * as types from "./error-constants";
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
|
||||
function beginFetch() {
|
||||
return { type: types.FETCH };
|
||||
}
|
||||
|
||||
function fetchSuccess(data) {
|
||||
return { type: types.FETCH_SUCCESS,
|
||||
data };
|
||||
}
|
||||
|
||||
function fetchError(data) {
|
||||
return { type: types.FETCH_ERROR,
|
||||
data };
|
||||
}
|
||||
|
||||
export function changeUrl(url) {
|
||||
return dispatch => {
|
||||
dispatch(push(url));
|
||||
};
|
||||
}
|
||||
|
||||
export function fetch(shortName) {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch(beginFetch());
|
||||
|
||||
return axios
|
||||
.get(process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/errors", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data) {
|
||||
var data ={
|
||||
shortName: shortName,
|
||||
errors: response.data
|
||||
}
|
||||
dispatch(fetchSuccess(data));
|
||||
} else {
|
||||
dispatch(fetchError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export const FETCH = "FETCH"
|
||||
export const FETCH_SUCCESS = "FETCH_SUCCESS"
|
||||
export const FETCH_ERROR = "FETCH_ERROR"
|
||||
@@ -1,42 +0,0 @@
|
||||
import {
|
||||
FETCH,
|
||||
FETCH_SUCCESS,
|
||||
FETCH_ERROR
|
||||
} from "./error-constants";
|
||||
|
||||
|
||||
|
||||
const error = (
|
||||
state = {
|
||||
isWaiting: false,
|
||||
items: {}
|
||||
},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
||||
case FETCH:
|
||||
|
||||
return Object.assign({}, state, { isWaiting: true});
|
||||
|
||||
|
||||
case FETCH_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: {
|
||||
...state.items,
|
||||
[action.data.shortName]: action.data.errors
|
||||
}
|
||||
});
|
||||
|
||||
case FETCH_ERROR:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default error;
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
|
||||
const FETCH_SUCCESS_STATISTIC = "FETCH_SUCCESS_STATISTIC"
|
||||
|
||||
const statistic = (
|
||||
state = {},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
||||
|
||||
case FETCH_SUCCESS_STATISTIC:
|
||||
return Object.assign({}, state, {...action.data});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default statistic;
|
||||
@@ -1,232 +0,0 @@
|
||||
import axios from "axios";
|
||||
//import { browserHistory } from "react-router"
|
||||
import * as types from "./system-constants";
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
|
||||
// "Log In" action creators
|
||||
function beginCreateSystem() {
|
||||
return { type: types.CREATE_SYSTEM };
|
||||
}
|
||||
|
||||
function createSystemSuccess(data) {
|
||||
return {
|
||||
type: types.CREATE_SUCCESS_SYSTEM,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
function createSystemError(data) {
|
||||
return {
|
||||
type: types.CREATE_ERROR_SYSTEM,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
function beginUpdateSystem() {
|
||||
return { type: types.UPDATE_SYSTEM };
|
||||
}
|
||||
|
||||
function updateSystemSuccess(data) {
|
||||
return { type: types.UPDATE_SUCCESS_SYSTEM, data };
|
||||
}
|
||||
|
||||
function updateSystemError(data) {
|
||||
return { type: types.UPDATE_ERROR_SYSTEM, data };
|
||||
}
|
||||
|
||||
function beginDeleteSystem() {
|
||||
return { type: types.DELETE_SYSTEM };
|
||||
}
|
||||
|
||||
function deleteSystemSuccess(data) {
|
||||
return { type: types.DELETE_SUCCESS_SYSTEM,
|
||||
data };
|
||||
}
|
||||
|
||||
function deleteSystemError() {
|
||||
return { type: types.DELETE_ERROR_SYSTEM };
|
||||
}
|
||||
|
||||
function beginFetchSystem() {
|
||||
return { type: types.FETCH_SYSTEM };
|
||||
}
|
||||
|
||||
function fetchSystemSuccess(data) {
|
||||
return { type: types.FETCH_SUCCESS_SYSTEM,
|
||||
data };
|
||||
}
|
||||
|
||||
function fetchStatisticSuccess(data) {
|
||||
return { type: types.FETCH_SUCCESS_STATISTIC,
|
||||
data };
|
||||
}
|
||||
|
||||
function fetchSystemError(data) {
|
||||
return { type: types.FETCH_ERROR_SYSTEM,
|
||||
data };
|
||||
}
|
||||
|
||||
function beginFetchError() {
|
||||
return { type: types.FETCH_ERROR };
|
||||
}
|
||||
|
||||
function fetchErrorsSuccess(data) {
|
||||
return { type: types.FETCH_SUCCESS_ERROR,
|
||||
data };
|
||||
}
|
||||
|
||||
function fetchErrorsError(data) {
|
||||
return { type: types.FETCH_ERROR_ERROR,
|
||||
data };
|
||||
}
|
||||
|
||||
function makeUserRequest(method, data, api = "/login") {
|
||||
// returns a Promise
|
||||
return axios({
|
||||
method: method,
|
||||
url: api,
|
||||
data: data,
|
||||
withCredentials: true
|
||||
});
|
||||
}
|
||||
|
||||
export function changeUrl(url) {
|
||||
return dispatch => {
|
||||
dispatch(push(url));
|
||||
};
|
||||
}
|
||||
|
||||
// Example of an Async Action Creator
|
||||
// http://redux.js.org/docs/advanced/AsyncActions.html
|
||||
|
||||
export function updateSystem(data) {
|
||||
return dispatch => {
|
||||
dispatch(beginUpdateSystem());
|
||||
|
||||
return makeUserRequest("post", data, process.env.REACT_APP_ADMIN_SERVER + "/systems/" + data.shortName, {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(updateSystemSuccess(response.data.system));
|
||||
} else {
|
||||
dispatch(updateSystemError());
|
||||
return response.data.message;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function shouldFetchSystems(state, subreddit) {
|
||||
if ((!state.system.items) || (state.system.items.length <1)) {
|
||||
return true
|
||||
} else if (state.system.isWaiting) {
|
||||
return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchErrors(shortName) {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
dispatch(beginFetchError());
|
||||
|
||||
return axios
|
||||
.get(process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/systems", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data) {
|
||||
dispatch(fetchErrorsSuccess(shortName, response.data));
|
||||
} else {
|
||||
dispatch(fetchErrorsError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchSystems() {
|
||||
return (dispatch, getState) => {
|
||||
if (shouldFetchSystems(getState())) {
|
||||
dispatch(beginFetchSystem());
|
||||
|
||||
return axios
|
||||
.get(process.env.REACT_APP_ADMIN_SERVER + "/systems", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(fetchSystemSuccess(response.data.systems));
|
||||
dispatch(fetchStatisticSuccess(response.data.stats));
|
||||
} else {
|
||||
dispatch(fetchSystemError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteSystem(shortName) {
|
||||
return dispatch => {
|
||||
dispatch(beginDeleteSystem());
|
||||
|
||||
return makeUserRequest("delete", null, process.env.REACT_APP_ADMIN_SERVER + "/systems/"+shortName)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(deleteSystemSuccess({shortName: shortName}));
|
||||
dispatch(push("/list-systems"));
|
||||
} else {
|
||||
dispatch(deleteSystemError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function createSystem(data) {
|
||||
return dispatch => {
|
||||
dispatch(beginCreateSystem());
|
||||
|
||||
return makeUserRequest("post", data, process.env.REACT_APP_ADMIN_SERVER + "/systems", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(createSystemSuccess(response.data.system));
|
||||
} else {
|
||||
dispatch(createSystemError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
export const CREATE_SYSTEM = "CREATE_SYSTEM"
|
||||
export const CREATE_SUCCESS_SYSTEM = "CREATE_SUCCESS_SYSTEM"
|
||||
export const CREATE_ERROR_SYSTEM = "CREATE_ERROR_SYSTEM"
|
||||
export const UPDATE_SYSTEM = "UPDATE_SYSTEM"
|
||||
export const UPDATE_SUCCESS_SYSTEM = "UPDATE_SUCCESS_SYSTEM"
|
||||
export const UPDATE_ERROR_SYSTEM = "UPDATE_ERROR_SYSTEM"
|
||||
export const DELETE_SYSTEM = "DELETE_SYSTEM"
|
||||
export const DELETE_SUCCESS_SYSTEM = "DELETE_SUCCESS_SYSTEM"
|
||||
export const DELETE_ERROR_SYSTEM = "DELETE_ERROR_SYSTEM"
|
||||
export const FETCH_SYSTEM = "FETCH_SYSTEM"
|
||||
export const FETCH_SUCCESS_SYSTEM = "FETCH_SUCCESS_SYSTEM"
|
||||
export const FETCH_ERROR_SYSTEM = "FETCH_ERROR_SYSTEM"
|
||||
export const FETCH_SUCCESS_STATISTIC = "FETCH_SUCCESS_STATISTIC"
|
||||
export const FETCH_ERROR = "FETCH_ERROR"
|
||||
export const FETCH_SUCCESS_ERROR = "FETCH_SUCCESS_ERROR"
|
||||
export const FETCH_ERROR_ERROR = "FETCH_ERROR_ERROR"
|
||||
@@ -1,93 +0,0 @@
|
||||
import {
|
||||
CREATE_SYSTEM,
|
||||
CREATE_SUCCESS_SYSTEM,
|
||||
CREATE_ERROR_SYSTEM,
|
||||
UPDATE_SYSTEM,
|
||||
UPDATE_SUCCESS_SYSTEM,
|
||||
UPDATE_ERROR_SYSTEM,
|
||||
DELETE_SYSTEM,
|
||||
DELETE_SUCCESS_SYSTEM,
|
||||
DELETE_ERROR_SYSTEM,
|
||||
FETCH_SYSTEM,
|
||||
FETCH_SUCCESS_SYSTEM,
|
||||
FETCH_ERROR_SYSTEM
|
||||
} from "./system-constants";
|
||||
|
||||
function updateObjectInArray(array, shortName, updatedItem) {
|
||||
return array.map( item => {
|
||||
if(item.shortName !== shortName) {
|
||||
// This isn't the item we care about - keep it as-is
|
||||
return item;
|
||||
}
|
||||
|
||||
// Otherwise, this is the one we want - return an updated value
|
||||
return {
|
||||
...item,
|
||||
...updatedItem
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const system = (
|
||||
state = {
|
||||
isWaiting: false,
|
||||
items: []},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
||||
case DELETE_SYSTEM:
|
||||
case UPDATE_SYSTEM:
|
||||
case FETCH_SYSTEM:
|
||||
case CREATE_SYSTEM:
|
||||
|
||||
return Object.assign({}, state, { isWaiting: true});
|
||||
|
||||
case DELETE_SUCCESS_SYSTEM:
|
||||
return Object.assign({}, state, {
|
||||
items: state.items.filter(item => item.shortName !== action.data.shortName)
|
||||
});
|
||||
|
||||
case UPDATE_SUCCESS_SYSTEM:
|
||||
return Object.assign({}, state, {
|
||||
items: updateObjectInArray(state.items, action.data.shortName, action.data)
|
||||
});
|
||||
case FETCH_SUCCESS_SYSTEM:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: action.data
|
||||
});
|
||||
case CREATE_SUCCESS_SYSTEM:
|
||||
const newItems = state.items.concat({
|
||||
name: action.data.name,
|
||||
shortName: action.data.shortName,
|
||||
description: action.data.description,
|
||||
systemType: action.data.systemType,
|
||||
city: action.data.city,
|
||||
state: action.data.state,
|
||||
county: action.data.county,
|
||||
country: action.data.country,
|
||||
key: action.data.key,
|
||||
userId: action.data.userId,
|
||||
id: action.data.id,
|
||||
showScreenName: action.data.showScreenName,
|
||||
ignoreUnknownTalkgroup : action.data.ignoreUnknownTalkgroup
|
||||
});
|
||||
return Object.assign({}, state, { isWaiting: false,
|
||||
items: newItems});
|
||||
|
||||
case DELETE_ERROR_SYSTEM:
|
||||
case UPDATE_ERROR_SYSTEM:
|
||||
case FETCH_ERROR_SYSTEM:
|
||||
case CREATE_ERROR_SYSTEM:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default system;
|
||||
@@ -1,163 +0,0 @@
|
||||
import axios, { post } from 'axios';
|
||||
//import { browserHistory } from "react-router"
|
||||
import * as types from "./talkgroup-constants";
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
// "Log In" action creators
|
||||
function beginImportTalkgroup() {
|
||||
return { type: types.IMPORT_TALKGROUP };
|
||||
}
|
||||
|
||||
function importTalkgroupSuccess(data) {
|
||||
return {
|
||||
type: types.IMPORT_SUCCESS_TALKGROUP,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
function importTalkgroupError(data) {
|
||||
return {
|
||||
type: types.IMPORT_ERROR_TALKGROUP,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
function beginExportTalkgroup() {
|
||||
return { type: types.EXPORT_TALKGROUP };
|
||||
}
|
||||
|
||||
function beginFetchTalkgroup() {
|
||||
return { type: types.FETCH_TALKGROUP };
|
||||
}
|
||||
|
||||
function fetchTalkgroupSuccess(data) {
|
||||
return { type: types.FETCH_SUCCESS_TALKGROUP,
|
||||
data };
|
||||
}
|
||||
|
||||
function fetchTalkgroupError(data) {
|
||||
return { type: types.FETCH_ERROR_TALKGROUP,
|
||||
data };
|
||||
}
|
||||
|
||||
export function changeUrl(url) {
|
||||
return dispatch => {
|
||||
dispatch(push(url));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/41878838/how-do-i-set-multipart-in-axios-with-react
|
||||
function fileUpload(shortName, file){
|
||||
const url = process.env.REACT_APP_ADMIN_SERVER + "/talkgroups/" + shortName + "/import";
|
||||
const formData = new FormData();
|
||||
formData.append('file',file)
|
||||
const config = {
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data'
|
||||
},
|
||||
withCredentials: true
|
||||
}
|
||||
return post(url, formData,config)
|
||||
}
|
||||
|
||||
// Example of an Async Action Creator
|
||||
// http://redux.js.org/docs/advanced/AsyncActions.html
|
||||
export function importTalkgroups(
|
||||
shortName, file,
|
||||
successPath // path to redirect to upon successful log in
|
||||
) {
|
||||
return dispatch => {
|
||||
dispatch(beginImportTalkgroup());
|
||||
|
||||
return fileUpload(shortName, file)
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
var data = {};
|
||||
data.shortName = shortName;
|
||||
data.talkgroups = response.data.talkgroups
|
||||
dispatch(importTalkgroupSuccess(data));
|
||||
// use browserHistory singleton to control navigation. Will generate a
|
||||
// state change for time-traveling as we are using the react-router-redux package
|
||||
//browserHistory.push(successPath)
|
||||
|
||||
} else {
|
||||
dispatch(importTalkgroupError(response.data.message));
|
||||
return response.data.message;
|
||||
}
|
||||
})
|
||||
.catch(function(response) {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
return response.message;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function exportTalkgroups(
|
||||
shortName
|
||||
) {
|
||||
return dispatch => {
|
||||
dispatch(beginExportTalkgroup());
|
||||
|
||||
return axios
|
||||
.get(process.env.REACT_APP_ADMIN_SERVER + "/talkgroups/" + shortName + "/export", { withCredentials: true })
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
var data = {shortName: shortName, talkgroups: response.data.talkgroups}
|
||||
dispatch(fetchTalkgroupSuccess( data));
|
||||
} else {
|
||||
dispatch(fetchTalkgroupError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
function shouldFetchTalkgroups(state, shortName) {
|
||||
if (!state.talkgroup.items.hasOwnProperty(shortName)) {
|
||||
return true
|
||||
} else if (state.talkgroup.isWaiting) {
|
||||
return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchTalkgroups(shortName) {
|
||||
return (dispatch, getState) => {
|
||||
if (shouldFetchTalkgroups(getState(), shortName)) {
|
||||
dispatch(beginFetchTalkgroup());
|
||||
|
||||
return axios
|
||||
.get(process.env.REACT_APP_ADMIN_SERVER + "/talkgroups/"+shortName, { withCredentials: true })
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
var data = {shortName: shortName, talkgroups: response.data.talkgroups}
|
||||
dispatch(fetchTalkgroupSuccess( data));
|
||||
} else {
|
||||
dispatch(fetchTalkgroupError());
|
||||
let registerMessage = response.data.message;
|
||||
return registerMessage;
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export const IMPORT_TALKGROUP = "IMPORT_TALKGROUP"
|
||||
export const IMPORT_SUCCESS_TALKGROUP = "IMPORT_SUCCESS_TALKGROUP"
|
||||
export const IMPORT_ERROR_TALKGROUP = "IMPORT_ERROR_TALKGROUP"
|
||||
export const EXPORT_TALKGROUP = "EXPORT_TALKGROUP"
|
||||
export const EXPORT_SUCCESS_TALKGROUP = "EXPORT_SUCCESS_TALKGROUP"
|
||||
export const EXPORT_ERROR_TALKGROUP = "EXPORT_ERROR_TALKGROUP"
|
||||
export const FETCH_TALKGROUP = "FETCH_TALKGROUP"
|
||||
export const FETCH_SUCCESS_TALKGROUP = "FETCH_SUCCESS_TALKGROUP"
|
||||
export const FETCH_ERROR_TALKGROUP = "FETCH_ERROR_TALKGROUP"
|
||||
@@ -1,51 +0,0 @@
|
||||
import {
|
||||
IMPORT_TALKGROUP,
|
||||
IMPORT_SUCCESS_TALKGROUP,
|
||||
IMPORT_ERROR_TALKGROUP,
|
||||
EXPORT_TALKGROUP,
|
||||
EXPORT_SUCCESS_TALKGROUP,
|
||||
EXPORT_ERROR_TALKGROUP,
|
||||
FETCH_TALKGROUP,
|
||||
FETCH_SUCCESS_TALKGROUP,
|
||||
FETCH_ERROR_TALKGROUP
|
||||
} from "./talkgroup-constants";
|
||||
|
||||
const talkgroup = (
|
||||
state = {
|
||||
isWaiting: false,
|
||||
items: {}},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
||||
case FETCH_TALKGROUP:
|
||||
case IMPORT_TALKGROUP:
|
||||
case EXPORT_TALKGROUP:
|
||||
return Object.assign({}, state, { isWaiting: true});
|
||||
|
||||
case IMPORT_SUCCESS_TALKGROUP:
|
||||
case FETCH_SUCCESS_TALKGROUP:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
items: {
|
||||
...state.items,
|
||||
[action.data.shortName]: action.data.talkgroups
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
case FETCH_ERROR_TALKGROUP:
|
||||
case IMPORT_ERROR_TALKGROUP:
|
||||
case EXPORT_ERROR_TALKGROUP:
|
||||
case EXPORT_SUCCESS_TALKGROUP:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default talkgroup;
|
||||
@@ -1,46 +0,0 @@
|
||||
import axios from "axios";
|
||||
//import { browserHistory } from "react-router"
|
||||
import * as types from "./user-constants";
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
|
||||
// "Log Out" action creators
|
||||
function beginLogout() {
|
||||
return { type: types.LOGOUT_USER };
|
||||
}
|
||||
|
||||
function logoutSuccess() {
|
||||
return { type: types.LOGOUT_SUCCESS_USER };
|
||||
}
|
||||
|
||||
function logoutError() {
|
||||
return { type: types.LOGOUT_ERROR_USER };
|
||||
}
|
||||
|
||||
// Example of an Async Action Creator
|
||||
// http://redux.js.org/docs/advanced/AsyncActions.html
|
||||
export function manualLogout() {
|
||||
return dispatch => {
|
||||
dispatch(beginLogout());
|
||||
|
||||
return axios
|
||||
.get(process.env.REACT_APP_ACCOUNT_SERVER + "/logout", {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
dispatch(logoutSuccess());
|
||||
// use browserHistory singleton to control navigation. Will generate a
|
||||
// state change for time-traveling as we are using the react-router-redux package
|
||||
//browserHistory.push("/") // logout to home page
|
||||
dispatch(push("/"));
|
||||
} else {
|
||||
dispatch(logoutError());
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened during logout that triggered an Error
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export const LOGIN_SUCCESS_USER = "LOGIN_SUCCESS_USER"
|
||||
export const LOGIN_EMAIL_ERROR_USER = "LOGIN_EMAIL_ERROR_USER"
|
||||
export const LOGIN_ERROR_USER = "LOGIN_ERROR_USER"
|
||||
export const LOGOUT_USER = "LOGOUT_USER"
|
||||
export const LOGOUT_SUCCESS_USER = "LOGOUT_SUCCESS_USER"
|
||||
export const LOGOUT_ERROR_USER = "LOGOUT_ERROR_USER"
|
||||
@@ -1,55 +0,0 @@
|
||||
import {
|
||||
LOGOUT_USER,
|
||||
LOGOUT_SUCCESS_USER,
|
||||
LOGOUT_ERROR_USER,
|
||||
LOGIN_SUCCESS_USER,
|
||||
LOGIN_ERROR_USER,
|
||||
LOGIN_EMAIL_ERROR_USER
|
||||
} from "./user-constants";
|
||||
|
||||
const user = (
|
||||
state = {
|
||||
isWaiting: false,
|
||||
authenticated: false,
|
||||
userId: ""
|
||||
},
|
||||
action
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case LOGIN_SUCCESS_USER:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
authenticated: true,
|
||||
...action.data
|
||||
});
|
||||
case LOGIN_EMAIL_ERROR_USER:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
authenticated: false,
|
||||
userId: action.data.userId
|
||||
});
|
||||
case LOGIN_ERROR_USER:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
authenticated: false
|
||||
});
|
||||
case LOGOUT_USER:
|
||||
return Object.assign({}, state, { isWaiting: true });
|
||||
case LOGOUT_SUCCESS_USER:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
authenticated: false,
|
||||
email: "",
|
||||
userId: ""
|
||||
});
|
||||
case LOGOUT_ERROR_USER:
|
||||
return Object.assign({}, state, {
|
||||
isWaiting: false,
|
||||
authenticated: true
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default user;
|
||||
201
admin/src/features/api/apiSlice.js
Normal file
201
admin/src/features/api/apiSlice.js
Normal file
@@ -0,0 +1,201 @@
|
||||
// Import the RTK Query methods from the React-specific entry point
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
/*
|
||||
const axiosBaseQuery =
|
||||
({ baseUrl } = { baseUrl: '' }) =>
|
||||
async ({ url, method, data, params }) => {
|
||||
try {
|
||||
const result = await axios({ url: baseUrl + url, method, data, params })
|
||||
return { data: result.data }
|
||||
} catch (axiosError) {
|
||||
let err = axiosError
|
||||
return {
|
||||
error: {
|
||||
status: err.response?.status,
|
||||
data: err.response?.data || err.message,
|
||||
},
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// Define our single API slice object
|
||||
export const apiSlice = createApi({
|
||||
// The cache reducer expects to be added at `state.api` (already default - this is optional)
|
||||
reducerPath: 'api',
|
||||
// All of our requests will have URLs starting with '/fakeApi'
|
||||
baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_ADMIN_SERVER }),
|
||||
// The "endpoints" represent operations and requests for this server
|
||||
endpoints: builder => ({
|
||||
// The `getPosts` endpoint is a "query" operation that returns data
|
||||
getTalkgroups: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `/talkgroups/${shortName}`, credentials: "include" })
|
||||
}),
|
||||
getGroups: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `/groups/${shortName}`, credentials: "include" }),
|
||||
providesTags: (result) =>
|
||||
result
|
||||
? [...result.map(({ _id }) => ({ type: 'Group', _id })), 'Group']
|
||||
: ['Group'],
|
||||
}),
|
||||
|
||||
getErrors: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `${process.env.REACT_APP_BACKEND_SERVER}/${shortName}/errors` })
|
||||
}),
|
||||
updateGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}/${group._id}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: group,
|
||||
}),
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: updatedGroup } = await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', updatedGroup.shortName, (groups) => {
|
||||
const groupIndex = groups.findIndex((obj => obj.groupId === updatedGroup.groupId));
|
||||
groups[groupIndex] = updatedGroup;
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
createGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: group,
|
||||
}),
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newGroup } = await queryFulfilled;
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', newGroup.shortName, (groups) => {
|
||||
groups.push(newGroup);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
deleteGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}/${group._id}`,
|
||||
method: 'DELETE',
|
||||
credentials: "include"
|
||||
}),
|
||||
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', group.shortName, (groups) => {
|
||||
const groupIndex = groups.findIndex((obj => obj.groupId === group.groupId));
|
||||
groups.splice(groupIndex,1);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
saveGroupOrder: builder.mutation({
|
||||
query: ({shortName, order}) => ({
|
||||
url: `/groups/${shortName}/reorder`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: order,
|
||||
}),
|
||||
|
||||
//invalidatesTags: (result, error, group) => ['Group'],
|
||||
}),
|
||||
importTalkgroups: builder.mutation({
|
||||
query: ({shortName,file}) => ({
|
||||
url: `${process.env.REACT_APP_ADMIN_SERVER}/talkgroups/${shortName}/import`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: file,
|
||||
}),
|
||||
|
||||
async onQueryStarted({shortName,file}, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newTalkgroups } = await queryFulfilled;
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getTalkgroups', shortName, (talkgroups) => {
|
||||
talkgroups.length = 0; // Clear your array
|
||||
talkgroups.push(...newTalkgroups);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
//invalidatesTags: (result, error, group) => ['Group'],
|
||||
}),
|
||||
getSystems: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: () => ({ url: `/systems`, credentials: "include" })
|
||||
}),
|
||||
createSystem: builder.mutation({
|
||||
query: (system) => ({
|
||||
url: `/systems`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: system,
|
||||
}),
|
||||
async onQueryStarted(system, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newSystem } = await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
systems.push(newSystem);
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
updateSystem: builder.mutation({
|
||||
query: (system) => ({
|
||||
url: `/systems/${system.shortName}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: system,
|
||||
}),
|
||||
async onQueryStarted(system, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: updatedSystem } = await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
const systemIndex = systems.findIndex((obj => obj.shortName === updatedSystem.shortName));
|
||||
systems[systemIndex] = updatedSystem;
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
deleteSystem: builder.mutation({
|
||||
query: (shortName) => ({
|
||||
url: `/systems/${shortName}`,
|
||||
method: 'DELETE',
|
||||
credentials: "include"
|
||||
}),
|
||||
async onQueryStarted(shortName, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
await queryFulfilled
|
||||
dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
const systemIndex = systems.findIndex((obj => obj.shortName === shortName));
|
||||
systems.splice(systemIndex, 1);
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
// Export the auto-generated hook for the `getPosts` query endpoint
|
||||
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice
|
||||
@@ -1,60 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
decrement,
|
||||
increment,
|
||||
incrementByAmount,
|
||||
incrementAsync,
|
||||
selectCount,
|
||||
} from './counterSlice';
|
||||
import styles from './Counter.module.css';
|
||||
|
||||
export function Counter() {
|
||||
const count = useSelector(selectCount);
|
||||
const dispatch = useDispatch();
|
||||
const [incrementAmount, setIncrementAmount] = useState('2');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.row}>
|
||||
<button
|
||||
className={styles.button}
|
||||
aria-label="Increment value"
|
||||
onClick={() => dispatch(increment())}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<span className={styles.value}>{count}</span>
|
||||
<button
|
||||
className={styles.button}
|
||||
aria-label="Decrement value"
|
||||
onClick={() => dispatch(decrement())}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<input
|
||||
className={styles.textbox}
|
||||
aria-label="Set increment amount"
|
||||
value={incrementAmount}
|
||||
onChange={e => setIncrementAmount(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className={styles.button}
|
||||
onClick={() =>
|
||||
dispatch(incrementByAmount(Number(incrementAmount) || 0))
|
||||
}
|
||||
>
|
||||
Add Amount
|
||||
</button>
|
||||
<button
|
||||
className={styles.asyncButton}
|
||||
onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))}
|
||||
>
|
||||
Add Async
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.row:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 78px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
margin-top: 2px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
.button {
|
||||
appearance: none;
|
||||
background: none;
|
||||
font-size: 32px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
outline: none;
|
||||
border: 2px solid transparent;
|
||||
color: rgb(112, 76, 182);
|
||||
padding-bottom: 4px;
|
||||
cursor: pointer;
|
||||
background-color: rgba(112, 76, 182, 0.1);
|
||||
border-radius: 2px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
font-size: 32px;
|
||||
padding: 2px;
|
||||
width: 64px;
|
||||
text-align: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.button:hover, .button:focus {
|
||||
border: 2px solid rgba(112, 76, 182, 0.4);
|
||||
}
|
||||
|
||||
.button:active {
|
||||
background-color: rgba(112, 76, 182, 0.2);
|
||||
}
|
||||
|
||||
.asyncButton {
|
||||
composes: button;
|
||||
position: relative;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.asyncButton:after {
|
||||
content: "";
|
||||
background-color: rgba(112, 76, 182, 0.15);
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
transition: width 1s linear, opacity 0.5s ease 1s;
|
||||
}
|
||||
|
||||
.asyncButton:active:after {
|
||||
width: 0%;
|
||||
opacity: 1;
|
||||
transition: 0s
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export const counterSlice = createSlice({
|
||||
name: 'counter',
|
||||
initialState: {
|
||||
value: 0,
|
||||
},
|
||||
reducers: {
|
||||
increment: state => {
|
||||
// Redux Toolkit allows us to write "mutating" logic in reducers. It
|
||||
// doesn't actually mutate the state because it uses the Immer library,
|
||||
// which detects changes to a "draft state" and produces a brand new
|
||||
// immutable state based off those changes
|
||||
state.value += 1;
|
||||
},
|
||||
decrement: state => {
|
||||
state.value -= 1;
|
||||
},
|
||||
incrementByAmount: (state, action) => {
|
||||
state.value += action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
|
||||
|
||||
// The function below is called a thunk and allows us to perform async logic. It
|
||||
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
|
||||
// will call the thunk with the `dispatch` function as the first argument. Async
|
||||
// code can then be executed and other actions can be dispatched
|
||||
export const incrementAsync = amount => dispatch => {
|
||||
setTimeout(() => {
|
||||
dispatch(incrementByAmount(amount));
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// The function below is called a selector and allows us to select a value from
|
||||
// the state. Selectors can also be defined inline where they're used instead of
|
||||
// in the slice file. For example: `useSelector((state) => state.counter.value)`
|
||||
export const selectCount = state => state.counter.value;
|
||||
|
||||
export default counterSlice.reducer;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user