frontend-session & message-for-author

master
Tudor Stanciu 2020-05-20 03:34:38 +03:00
parent 423f79f04b
commit 432efcffe3
16 changed files with 285 additions and 21 deletions

View File

@ -20,7 +20,8 @@
"About": "About"
},
"General": {
"Close": "Close"
"Close": "Close",
"Send": "Send"
},
"Session": {
"Title": "Sessions",
@ -59,6 +60,10 @@
"Domain": "Domain",
"ActiveSession": "Active session",
"ActiveSessionSubtitle": "Expand to see forwards",
"DDNSProvider": "Dynamic DNS Provider"
"DDNSProvider": "Dynamic DNS Provider",
"Actions": {
"SendMessageToAuthor": "Send message to author"
},
"MessageForAuthor": "Message for author"
}
}

View File

@ -11,7 +11,8 @@
"About": "Despre"
},
"General": {
"Close": "Închide"
"Close": "Închide",
"Send": "Trimite"
},
"Session": {
"Title": "Sesiuni",
@ -50,6 +51,10 @@
"Domain": "Domeniu",
"ActiveSession": "Sesiune activă",
"ActiveSessionSubtitle": "Extindeţi pentru a vedea redirectările",
"DDNSProvider": "Furnizor DNS dinamic"
"DDNSProvider": "Furnizor DNS dinamic",
"Actions": {
"SendMessageToAuthor": "Trimite mesaj către autor"
},
"MessageForAuthor": "Mesaj pentru autor"
}
}

View File

@ -1,4 +1,5 @@
import React, { Suspense } from "react";
import React, { Suspense, useEffect } from "react";
import PropTypes from "prop-types";
import { Route, Switch } from "react-router-dom";
import HomePage from "./home/HomePage";
import Header from "./layout/Header";
@ -8,8 +9,15 @@ import "react-toastify/dist/ReactToastify.css";
import SessionContainer from "../features/session/components/SessionContainer";
import ReleaseNotesContainer from "../features/releaseNotes/components/ReleaseNotesContainer";
import AboutContainer from "../features/about/components/AboutContainer";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { loadFrontendSession } from "../features/frontendSession/actionCreators";
function App({ actions }) {
useEffect(() => {
actions.loadFrontendSession();
}, []);
function App() {
const contentStyle = {
paddingLeft: "30px",
paddingRight: "30px"
@ -33,4 +41,18 @@ function App() {
);
}
export default App;
App.propTypes = {
actions: PropTypes.object.isRequired
};
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ loadFrontendSession }, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

View File

@ -0,0 +1,18 @@
import * as types from "./actionTypes";
import api from "./api";
import { sendHttpRequest } from "../../redux/actions/httpActions";
export function loadFrontendSession() {
return async function (dispatch) {
try {
dispatch({ type: types.INITIALIZE_FRONTEND_SESSION_STARTED });
const data = await dispatch(sendHttpRequest(api.getFrontendSession()));
dispatch({
type: types.INITIALIZE_FRONTEND_SESSION_SUCCESS,
payload: data
});
} catch (error) {
throw error;
}
};
}

View File

@ -0,0 +1,5 @@
export const INITIALIZE_FRONTEND_SESSION_STARTED =
"INITIALIZE_FRONTEND_SESSION_STARTED";
export const INITIALIZE_FRONTEND_SESSION_SUCCESS =
"INITIALIZE_FRONTEND_SESSION_SUCCESS";

View File

@ -0,0 +1,8 @@
import { get } from "../../api/axiosApi";
const baseUrl = process.env.REVERSE_PROXY_API_URL;
const getFrontendSession = () => get(`${baseUrl}/system/frontend-session`);
export default {
getFrontendSession
};

View File

@ -0,0 +1,26 @@
import * as types from "./actionTypes";
import initialState from "../../redux/reducers/initialState";
export default function frontendSessionReducer(
state = initialState.frontendSession,
action
) {
switch (action.type) {
case types.INITIALIZE_FRONTEND_SESSION_STARTED:
return {
loading: true,
loaded: false
};
case types.INITIALIZE_FRONTEND_SESSION_SUCCESS:
return {
...state,
...action.payload,
loading: false,
loaded: true
};
default:
return state;
}
}

View File

@ -0,0 +1,17 @@
import * as types from "./actionTypes";
import api from "./api";
import { sendHttpRequest } from "../../redux/actions/httpActions";
export function saveMessageForAuthor(messageContent) {
return async function (dispatch, getState) {
try {
const sessionId = getState().frontendSession.sessionId;
const data = await dispatch(
sendHttpRequest(api.saveMessageForAuthor(sessionId, messageContent))
);
dispatch({ type: types.SAVE_MESSAGE_FOR_AUTHOR_SUCCESS, payload: data });
} catch (error) {
throw error;
}
};
}

View File

@ -0,0 +1,2 @@
export const SAVE_MESSAGE_FOR_AUTHOR_SUCCESS =
"SAVE_MESSAGE_FOR_AUTHOR_SUCCESS";

View File

@ -0,0 +1,9 @@
import { post } from "../../api/axiosApi";
const baseUrl = process.env.REVERSE_PROXY_API_URL;
const saveMessageForAuthor = (sessionId, messageContent) =>
post(`${baseUrl}/system/message-for-author`, { sessionId, messageContent });
export default {
saveMessageForAuthor
};

View File

@ -0,0 +1,52 @@
import React, { useState } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import PropTypes from "prop-types";
import MessageForAuthorDialog from "./MessageForAuthorDialog";
import { saveMessageForAuthor } from "../actionCreators";
const MessageForAuthorContainer = ({ actions, open, handleClose }) => {
const [messageForAuthor, setMessageForAuthor] = useState("");
const onMessageForAuthorChanged = (event) => {
const value = event.target.value;
setMessageForAuthor(value);
};
const saveMessage = () => {
actions.saveMessageForAuthor(messageForAuthor);
setMessageForAuthor("");
handleClose();
};
return (
<MessageForAuthorDialog
open={open}
handleClose={handleClose}
messageForAuthor={messageForAuthor}
onMessageForAuthorChanged={onMessageForAuthorChanged}
saveMessage={saveMessage}
/>
);
};
MessageForAuthorContainer.propTypes = {
actions: PropTypes.object.isRequired,
open: PropTypes.bool.isRequired,
handleClose: PropTypes.func.isRequired
};
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ saveMessageForAuthor }, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MessageForAuthorContainer);

View File

@ -0,0 +1,69 @@
import React from "react";
import PropTypes from "prop-types";
import TextField from "@material-ui/core/TextField";
import {
Dialog,
DialogContent,
IconButton,
Grid,
Tooltip
} from "@material-ui/core";
import SendRoundedIcon from "@material-ui/icons/SendRounded";
import { useTranslation } from "react-i18next";
const MessageForAuthorDialog = ({
open,
handleClose,
messageForAuthor,
onMessageForAuthorChanged,
saveMessage
}) => {
const { t } = useTranslation();
return (
<div>
<Dialog
fullWidth
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogContent>
<Grid container>
<Grid item xs={11} sm={11} md={11}>
<TextField
autoFocus
id="message-for-author"
label={t("Server.MessageForAuthor")}
fullWidth
value={messageForAuthor}
onChange={onMessageForAuthorChanged}
/>
</Grid>
<Grid item xs={1} sm={1} md={1}>
<Tooltip title={t("General.Send")}>
<IconButton
aria-label="send message"
onClick={saveMessage}
color="primary"
>
<SendRoundedIcon />
</IconButton>
</Tooltip>
</Grid>
</Grid>
</DialogContent>
</Dialog>
</div>
);
};
MessageForAuthorDialog.propTypes = {
open: PropTypes.bool.isRequired,
handleClose: PropTypes.func.isRequired,
messageForAuthor: PropTypes.string.isRequired,
onMessageForAuthorChanged: PropTypes.func.isRequired,
saveMessage: PropTypes.func.isRequired
};
export default MessageForAuthorDialog;

View File

@ -10,13 +10,14 @@ import {
Collapse,
Avatar,
IconButton,
Typography
Typography,
Tooltip
} from "@material-ui/core";
import FavoriteIcon from "@material-ui/icons/Favorite";
import ShareIcon from "@material-ui/icons/Share";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import MoreVertIcon from "@material-ui/icons/MoreVert";
import DnsRoundedIcon from "@material-ui/icons/DnsRounded";
import MessageRoundedIcon from "@material-ui/icons/MessageRounded";
import styles from "../../../components/common/styles/expandableCardStyles";
import ServerSummary from "./ServerSummary";
import { useTranslation } from "react-i18next";
@ -27,7 +28,8 @@ const ServerComponent = ({
data,
serverHost,
openAbout,
handleOpenInNewTab
handleOpenInNewTab,
showMessageForAuthor
}) => {
const classes = useStyles();
const { t } = useTranslation();
@ -65,9 +67,11 @@ const ServerComponent = ({
)}
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
<Tooltip title={t("Server.Actions.SendMessageToAuthor")}>
<IconButton aria-label="sent message" onClick={showMessageForAuthor}>
<MessageRoundedIcon />
</IconButton>
</Tooltip>
<IconButton aria-label="share">
<ShareIcon />
</IconButton>
@ -121,7 +125,8 @@ ServerComponent.propTypes = {
data: PropTypes.object.isRequired,
serverHost: PropTypes.string,
openAbout: PropTypes.func.isRequired,
handleOpenInNewTab: PropTypes.func.isRequired
handleOpenInNewTab: PropTypes.func.isRequired,
showMessageForAuthor: PropTypes.func.isRequired
};
export default ServerComponent;

View File

@ -1,12 +1,23 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import PropTypes from "prop-types";
import { loadServerData, loadSystemVersion } from "../actionCreators";
import ServerComponent from "./ServerComponent";
import { withRouter } from "react-router-dom";
import MessageForAuthorContainer from "../../messageForAuthor/components/MessageForAuthorContainer";
const ServerContainer = ({ actions, data, serverHost, history }) => {
const [openMessageForAuthor, setOpenMessageForAuthor] = useState(false);
const closeMessageForAuthor = () => {
setOpenMessageForAuthor(false);
};
const showMessageForAuthor = () => {
setOpenMessageForAuthor(true);
};
useEffect(() => {
actions.loadServerData();
actions.loadSystemVersion();
@ -23,12 +34,19 @@ const ServerContainer = ({ actions, data, serverHost, history }) => {
};
return (
<>
<ServerComponent
data={data}
serverHost={serverHost}
openAbout={openAbout}
handleOpenInNewTab={handleOpenInNewTab}
showMessageForAuthor={showMessageForAuthor}
/>
<MessageForAuthorContainer
open={openMessageForAuthor}
handleClose={closeMessageForAuthor}
/>
</>
);
};

View File

@ -6,8 +6,10 @@ import {
forwardsReducer
} from "../../features/session/reducers";
import releaseNotesReducer from "../../features/releaseNotes/reducer";
import frontendSessionReducer from "../../features/frontendSession/reducer";
const rootReducer = combineReducers({
frontendSession: frontendSessionReducer,
server: serverReducer,
sessions: sessionsReducer,
forwards: forwardsReducer,

View File

@ -1,4 +1,5 @@
export default {
frontendSession: { loading: false, loaded: false },
server: {
data: { loading: false, loaded: false },
activeSession: { loading: false, loaded: false }