@@ -44,11 +43,13 @@ export default class FeedbackResult extends Component {
) : (
- Thank you! Your feedback is greatly appreciated.
-
Your ticket key:
+ Thank you! Your feedback is greatly appreciated. Please
+ remember to save your ticket id as it will not be shown again!
+
+
Ticket id:
{message}
- copy key
+ copy id
@@ -56,10 +57,10 @@ export default class FeedbackResult extends Component {
type="submit"
className="btn btn-primary access-link filled1"
onClick={() => {
- newWindowLocation(FEEDBACK_CHAT(message));
+ this.props.history.push(FEEDBACK_CHAT(message));
}}
>
-
Access my ticket
+
Access this ticket
diff --git a/frontend/src/components/common/FeedbackTicket.jsx b/frontend/src/components/common/FeedbackTicket.jsx
deleted file mode 100644
index 936fc106..00000000
--- a/frontend/src/components/common/FeedbackTicket.jsx
+++ /dev/null
@@ -1,322 +0,0 @@
-import dateformat from "dateformat";
-import PropTypes from "prop-types";
-import React, { Component } from "react";
-import send from "../../assets/images/feedback/send.png";
-import { ANONYMOUS_LAST_SEEN, USER_LAST_SEEN } from "../../constants/strings";
-import { DEFAULT_USERNAME, DEFAULT_USER_ID } from "../../constants/user";
-import { getCurrentUser } from "../../services/http/authService";
-import {
- postFeedbackMessage,
- updateSeenAt
-} from "../../services/http/feedbackService";
-import { connectSocket } from "../../services/socket/base";
-import {
- emitMessage,
- emitSeen,
- onErrorReceived,
- onMessageReceived,
- onSeen
-} from "../../services/socket/chat";
-import { validateInputMessage } from "../../utils/strings";
-import { ANONYMOUS_USER } from "../../utils/user";
-import TicketMessage from "./TicketMessage";
-
-export default class FeedbackTicket extends Component {
- static propTypes = {
- /**
- * Feedback info returned from server after it is initially created
- */
- feedback: PropTypes.shape({
- id: PropTypes.string,
- isClosed: PropTypes.bool
- }),
-
- /**
- * Messages related to specific feedback
- */
- messages: PropTypes.array.isRequired
- };
-
- state = {
- messages: [],
- inputMessage: "",
- latestAuthorName: "",
- user: {
- name: "",
- id: ""
- },
- isMessageSubmitting: false,
- seen: false,
- /**
- * Checks if this client is the author of received chat message
- */
- isAuthorCurrentClient: false,
- error: ""
- };
-
- resolveCurrentUser = () => {
- return getCurrentUser() ? getCurrentUser() : ANONYMOUS_USER;
- };
-
- resolveAuthorName = message => {
- return message.User ? message.User.name : ANONYMOUS_USER.name;
- };
-
- /**
- * Scroll to bottom of chat container
- */
- scrollToBottom = () => {
- this.messagesEnd.scrollIntoView();
- };
-
- /**
- * Called when server emits a message through socket
- *
- * @param {Object} data corresponds to message model from the server
- */
- onChatMessageReceived = data => {
- const { messages, user } = this.state;
- if (!this.state.isAuthorCurrentClient) {
- messages.push(data);
- const latestAuthorName = this.resolveAuthorName(data);
- this.setState({
- messages,
- isMessageSubmitting: false,
- seen: false,
- latestAuthorName
- });
- }
- };
-
- /**
- * Called when server emits an error through socket
- *
- * @param {String} error error message
- */
- onChatErrorReceived = error => {
- this.state.socket.disconnect();
- this.setState({ isMessageSubmitting: false });
- };
-
- /**
- * Called when seen is transmitted
- */
- onSeenReceived = ({ user, date }) => {
- const currentUser = this.resolveCurrentUser();
- const { latestAuthorName } = this.state;
- if (
- user.name !== currentUser.name &&
- latestAuthorName === currentUser.name
- ) {
- this.setState({ seen: true });
- }
- };
-
- /**
- * If error occured in a socket
- * @param {Object} err
- */
- onEmitSeenError(err) {
- this.state.socket.disconnect();
- }
-
- /**
- * Returns true if latest message was seen by user on opposite end, and
- * if current user is author of latest message
- *
- * @param {String} messageCreatedAt when message was created
- * @param {String} latestAuthorName author of latest message
- * @param {String} feedbackSeenAt when user on opposite end had seen the message
- * @param {String} currentUser represents if current client is anonymous or logged in
- */
- isSeen = (
- messageCreatedAt,
- latestAuthorName,
- feedbackSeenAt,
- currentUser
- ) => {
- if (
- new Date(feedbackSeenAt) >= new Date(messageCreatedAt) &&
- latestAuthorName === currentUser
- ) {
- return true;
- }
- return false;
- };
-
- updateMountState = (socket, user, { feedback, messages }) => {
- const lastMessage = messages[messages.length - 1];
- const latestAuthorName = this.resolveAuthorName(lastMessage);
- this.setState({
- user,
- messages,
- latestAuthorName,
- seen: this.isSeen(
- lastMessage.createdAt,
- latestAuthorName,
- user.name !== DEFAULT_USERNAME
- ? feedback.anonymLastSeenAt
- : feedback.userLastSeenAt,
- user.name
- ),
- socket,
- error: ""
- });
- };
-
- componentDidMount() {
- this.scrollToBottom();
- const socket = connectSocket();
- const user = this.resolveCurrentUser();
- const { feedback } = this.props;
- this.updateMountState(socket, user, this.props);
- onMessageReceived(feedback.id, this.onChatMessageReceived);
- onErrorReceived(user ? user.id : DEFAULT_USER_ID, this.onChatErrorReceived);
- onSeen(feedback.id, this.onSeenReceived);
- this.updateSeenInfo();
- window.addEventListener("focus", this.onFocus);
- }
-
- updateSeenInfo = () => {
- const user = this.resolveCurrentUser();
- const { id } = this.props.feedback;
- const date = new Date();
- const payload = {
- [user.name !== DEFAULT_USERNAME
- ? USER_LAST_SEEN
- : ANONYMOUS_LAST_SEEN]: date
- };
- updateSeenAt(id, payload)
- .catch(err => this.onUpdateSeenError(err))
- .then(res => this.onUpdateSeenSuccess(res.result, user, id, date))
- .catch(err => this.onEmitSeenError(err));
- };
-
- componentDidUpdate() {
- this.scrollToBottom();
- }
-
- componentWillUnmount() {
- window.removeEventListener("focus", this.onFocus);
- }
-
- onFocus = () => {
- this.updateSeenInfo();
- };
-
- onUpdateSeenSuccess(res, user, feedbackId, date) {
- emitSeen(user, feedbackId, date);
- }
-
- /**
- * If error occured during REST call
- * @param {Object} err
- */
- onUpdateSeenError(err) {
- if (err.message.includes("Failed to fetch")) {
- return;
- }
- this.setState({ error: err.message });
- }
-
- onSendMessage = e => {
- e.preventDefault();
- const { inputMessage, user } = this.state;
- if (inputMessage === "") return;
- const error = validateInputMessage(inputMessage);
- if (error) {
- this.setState({ error });
- return;
- }
- const { id } = this.props.feedback;
- this.setState({ isMessageSubmitting: true });
- postFeedbackMessage(id, user.id === DEFAULT_USER_ID ? "" : user.id, {
- text: inputMessage
- })
- .then(res => this.onPostFeedbackMessageSuccess(res.result))
- .catch(err => this.onPostFeedbackMessageError(err));
- };
-
- onPostFeedbackMessageSuccess = res => {
- const { messages } = this.state;
- if (res.User == null) res.User = ANONYMOUS_USER;
- messages.push(res);
- const lastMessage = messages[messages.length - 1];
- const latestAuthorName = this.resolveAuthorName(lastMessage);
- this.setState({
- messages,
- isMessageSubmitting: false,
- isAuthorCurrentClient: true,
- seen: false,
- latestAuthorName,
- inputMessage: ""
- });
- emitMessage(this.props.feedback.id, res);
- };
-
- onPostFeedbackMessageError = err => {
- this.setState({ error: err.message, isMessageSubmitting: false });
- };
-
- render() {
- const {
- inputMessage,
- messages,
- seen,
- isMessageSubmitting,
- error
- } = this.state;
- const { feedback } = this.props;
- return (
-
-
- Created at: {dateformat(feedback.createdAt, "dd.mm.yyyy. HH:MM")}
-
-
-
- {messages.map((item, index) => (
-
- ))}
-
{
- this.messagesEnd = el;
- }}
- >
- {error}
-
-
-
-
- );
- }
-}
diff --git a/frontend/src/components/common/ModifyAccountForm.jsx b/frontend/src/components/common/ModifyAccountForm.jsx
new file mode 100644
index 00000000..8eca2412
--- /dev/null
+++ b/frontend/src/components/common/ModifyAccountForm.jsx
@@ -0,0 +1,112 @@
+import Joi from "joi-browser";
+import PropTypes from "prop-types";
+import React from "react";
+import { CHANGE } from "../../constants/form/labels/button";
+import {
+ CONFIRM_PASSWORD_LABEL,
+ NEW_PASSWORD_LABEL,
+} from "../../constants/form/labels/input";
+import {
+ CONFIRM_PASSWORD,
+ NEW_PASSWORD,
+} from "../../constants/form/names/input";
+import Form from "./ui/form/Form";
+import LoadingSpinner from "./ui/LoadingSpinner";
+
+export default class ModifyAccountForm extends Form {
+ static propTypes = {
+ /**
+ * Error to display if submit was a failure
+ */
+ error: PropTypes.object,
+
+ /**
+ * Success to display if submit was a success
+ */
+ success: PropTypes.string,
+
+ /**
+ * Is sign up info submitting
+ */
+ isSubmitting: PropTypes.bool.isRequired,
+ };
+
+ state = {
+ submitPressed: false,
+ data: {
+ newPassword: "",
+ confirmPassword: "",
+ },
+ errors: {},
+ };
+
+ schema = {
+ newPassword: Joi.string().required().label(NEW_PASSWORD_LABEL).min(8),
+ confirmPassword: Joi.valid(Joi.ref(NEW_PASSWORD))
+ .options({
+ language: { any: { allowOnly: "must match password" } },
+ })
+ .label(CONFIRM_PASSWORD_LABEL),
+ };
+
+ onSubmit = (e) => {
+ this.handleSubmit(e, this.props.onSubmit);
+ };
+
+ render() {
+ const { error, isSubmitting, success } = this.props;
+ return (
+
+ );
+ }
+}
diff --git a/frontend/src/components/common/Navbar.jsx b/frontend/src/components/common/Navbar.jsx
index a6d43647..879c7579 100644
--- a/frontend/src/components/common/Navbar.jsx
+++ b/frontend/src/components/common/Navbar.jsx
@@ -4,7 +4,12 @@ import { Nav, Navbar } from "react-bootstrap";
import { NavLink } from "react-router-dom";
import { ReactComponent as Logo } from "../../assets/images/logo.svg";
import { TOKEN_HEADER } from "../../constants/headers";
-import { FEEDBACKS_ROUTE_PAGE, FEEDBACK_ROUTE } from "../../constants/routes";
+import {
+ ACCOUNT_ROUTE,
+ FEEDBACKS_ROUTE,
+ FEEDBACK_ROUTE,
+ SIGNUP_ROUTE,
+} from "../../constants/routes";
import { signOut } from "../../services/http/authService";
import { newWindowLocation } from "../../utils/navigate";
@@ -13,13 +18,13 @@ export default class NavBar extends Component {
/**
* User that is currently signed in
*/
- user: PropTypes.object
+ user: PropTypes.object,
};
onSignOut = () => {
signOut()
- .then(res => this.onSignOutSuccessful())
- .catch(err => this.onSignOutError(err));
+ .then((res) => this.onSignOutSuccessful())
+ .catch((err) => this.onSignOutError(err));
};
onSignOutSuccessful = () => {
@@ -28,7 +33,7 @@ export default class NavBar extends Component {
newWindowLocation(FEEDBACK_ROUTE);
};
- onSignOutError = error => {
+ onSignOutError = (error) => {
alert(error);
localStorage.removeItem(TOKEN_HEADER);
newWindowLocation(FEEDBACK_ROUTE);
@@ -46,12 +51,18 @@ export default class NavBar extends Component {