closer to working

This commit is contained in:
Luke Berndt
2023-03-26 09:27:05 -04:00
parent a5888e412c
commit 5875364502
17 changed files with 6932 additions and 27717 deletions

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}

View File

@@ -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 = "http://admin.openmhz.test:3000"
var dev_server = "http://account.openmhz.test:3000"
module.exports = function(app, passport) {
app.set("port", 3009)
@@ -26,8 +25,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 +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
})
}

View File

@@ -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,6 +9,14 @@ 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';
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");
@@ -19,6 +24,7 @@ exports.isLoggedIn = 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());

View File

@@ -1,15 +1,39 @@
import React, { Component } from "react"
import NavigationContainer from "./NavigationContainer"
import React, { Component } from "react"
import Navigation from "./Navigation"
import { Routes, Route } from 'react-router-dom';
class App extends Component {
render() {
return(
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) => {
const {store} = params;
return (
<div>
<NavigationContainer />
{this.props.children}
<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

View File

@@ -1,53 +1,44 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Menu, Dropdown } from "semantic-ui-react";
import React, { useEffect } from "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="/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;

View File

@@ -1,55 +1,32 @@
// 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();
useEffect(() => {
dispatch(authenticateUser({}));
},[]);
}
}
render() {
return <BaseComponent {...this.props} />;
}
}
return withRouter(Restricted);
if (hasAuthenticated) {
if (authenticated) {
return children;;
}
else {
navigate("/list-systems")
}
}
export default restricted
return <div/>
};
export default Restricted

View File

@@ -1,5 +1,7 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import React, { useEffect, useState, } from "react";
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
import { loginUser } from "../features/user/userSlice";
import {
Container,
@@ -26,31 +28,26 @@ 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, setSearchParams] = useSearchParams();
const nextLocation = searchParams.get("nextLocation");
state = {
loginMessage: "",
email: "",
password: ""
};
handleRegisterClick = () => {
this.props.changeUrl("/register");
};
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
_onLoginSubmit(event) {
const handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
const loginSubmit = (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) {
if (nextLocation) {
success.type = "location";
switch (this.props.nextLocation) {
case "frontend":
@@ -81,11 +78,10 @@ class Login extends Component {
loginMessage
});
}
});
});*/
}
render() {
var loginMessage = "";
/*
if (this.state.loginMessage) {
loginMessage = (
@@ -98,7 +94,7 @@ class Login extends Component {
</Message>
);
}
*/
return (
<Container>
<Header as="h1">{process.env.REACT_APP_SITE_NAME}</Header>
@@ -147,12 +143,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 +156,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 +167,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>
@@ -190,6 +188,6 @@ class Login extends Component {
</Container>
);
}
}
export default Login;

View File

@@ -0,0 +1,202 @@
// Import the RTK Query methods from the React-specific entry point
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { createSelector } from '@reduxjs/toolkit'
/*
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, 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
const patchResult = 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;
const patchResult = 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
const patchResult = 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;
const patchResult = 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
const patchResult = 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
const patchResult = 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
const patchResult = 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

View File

@@ -0,0 +1,107 @@
import {
createSlice,
createEntityAdapter,
createSelector,
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 ({ getState }) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/login"
const res = await fetch(url, {
method: 'POST',
credentials: "include"
}).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 userSlice = createSlice({
name: 'user',
initialState: {
isWaiting: false,
authenticated: false,
hasAuthenticated: false,
userId: "",
admin: false,
email: "",
},
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: {
[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;
} 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;

View File

@@ -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>
)

View File

@@ -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;

File diff suppressed because it is too large Load Diff

View File

@@ -27,22 +27,6 @@ const App = (params) => {
</Routes>
</div>
)
/*
return (
<div>
<NavigationContainer />
<Routes>
<Route path="/about" element={<About />} component={Restricted(About, store)} />
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
<Route path="/update-system/:shortName" component={Restricted(UpdateSystemContainer, store)} />
<Route path="/create-system" component={Restricted(CreateSystemContainer, store)} />
<Route path="/list-systems" component={Restricted(ListSystemsContainer, store)} />
<Route path="/" component={Restricted(ListSystemsContainer, store)} />
<Route render={() => (<div>Miss</div>)} />
</Routes>
</div>
)*/
};
export default App

View File

@@ -2,6 +2,7 @@
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
*/
@@ -9,14 +10,21 @@ import { authenticateUser } from "../features/user/userSlice";
const Restricted = ({children }) => {
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
const dispatch = useDispatch();
const navigate = useNavigate();
useEffect(() => {
dispatch(authenticateUser({}));
},[]);
if (authenticated) {
return children;;
}
if (hasAuthenticated) {
if (authenticated) {
return children;;
}
else {
navigate("/list-systems")
}
}
return <div/>
};

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from "react";
import React, { useEffect, useState, } from "react";
import { useGetSystemsQuery, useGetTalkgroupsQuery, useGetGroupsQuery, useUpdateGroupMutation, useCreateGroupMutation, useDeleteGroupMutation, useSaveGroupOrderMutation} from '../features/api/apiSlice'
import {
@@ -105,7 +105,6 @@ const GroupModal = (props) => {
setTalkgroups(talkgroups);
}
const handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
const addTalkgroup = (event, index) => {
const item = {